Skip to content

Commit

Permalink
Refactor the HLS fallback logic to support a ternary fallback type
Browse files Browse the repository at this point in the history
This adds all the logic to falling back to either MediaPlayer or the
builtin HLSDemuxer, even if those aren't ready yet. Enabling the flag
won't do anything even in this patch, and two TODOs are left where those
changes would have to be made. As such, the kBuiltinHlsPlayer option
for fallback is marked as unreachable, for now.

R=tguilbert

Bug: 1266991
Change-Id: I902e36711435c292a31961528e03cdbf83ab0427
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/4297439
Reviewed-by: Thomas Guilbert <tguilbert@chromium.org>
Commit-Queue: Ted (Chromium) Meyer <tmathmeyer@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1116629}
  • Loading branch information
tm-chromium authored and Chromium LUCI CQ committed Mar 13, 2023
1 parent fa05ecb commit ec47185
Show file tree
Hide file tree
Showing 6 changed files with 131 additions and 69 deletions.
4 changes: 2 additions & 2 deletions media/base/media_switches.cc
Expand Up @@ -1011,14 +1011,14 @@ BASE_FEATURE(kUseRealColorSpaceForAndroidVideo,
"UseRealColorSpaceForAndroidVideo",
base::FEATURE_ENABLED_BY_DEFAULT);

#endif // BUILDFLAG(IS_ANDROID)

#if BUILDFLAG(ENABLE_HLS_DEMUXER)
BASE_FEATURE(kBuiltInHlsPlayer,
"BuiltInHlsPlayer",
base::FEATURE_DISABLED_BY_DEFAULT);
#endif // BUILDFLAG(ENABLE_HLS_DEMUXER)

#endif // BUILDFLAG(IS_ANDROID)

#if BUILDFLAG(USE_CHROMEOS_MEDIA_ACCELERATION)
// Enable hardware AV1 decoder on ChromeOS.
BASE_FEATURE(kChromeOSHWAV1Decoder,
Expand Down
7 changes: 3 additions & 4 deletions media/base/media_switches.h
Expand Up @@ -315,19 +315,18 @@ MEDIA_EXPORT BASE_DECLARE_FEATURE(kRequestSystemAudioFocus);
MEDIA_EXPORT BASE_DECLARE_FEATURE(kUseAudioLatencyFromHAL);
MEDIA_EXPORT BASE_DECLARE_FEATURE(kUsePooledSharedImageVideoProvider);
MEDIA_EXPORT BASE_DECLARE_FEATURE(kUseRealColorSpaceForAndroidVideo);
#endif // BUILDFLAG(IS_ANDROID)

#if BUILDFLAG(ENABLE_HLS_DEMUXER)
// The feature |kHlsPlayer| enables the use of Android's builtin media-player
// based HLS implementation, which chrome currently relies on when playing
// on android, while this feature enabled chrome's built-in HLS parser and
// demuxer. When this feature is enabled, the media-player based HLS player
// will NOT be used. This will roll out first on android (hence inside the
// IS_ANDROID buildflag), but will eventually land in desktop chrome as well.
// will NOT be used. This will roll out first on android, but will eventually
// land in desktop chrome as well.
MEDIA_EXPORT BASE_DECLARE_FEATURE(kBuiltInHlsPlayer);
#endif // BUILDFLAG(ENABLE_HLS_DEMUXER)

#endif // BUILDFLAG(IS_ANDROID)

#if BUILDFLAG(USE_CHROMEOS_MEDIA_ACCELERATION)
MEDIA_EXPORT BASE_DECLARE_FEATURE(kChromeOSHWAV1Decoder);
MEDIA_EXPORT BASE_DECLARE_FEATURE(kChromeOSHWVBREncoding);
Expand Down
145 changes: 96 additions & 49 deletions media/filters/demuxer_manager.cc
Expand Up @@ -24,7 +24,7 @@ namespace media {

namespace {

#if BUILDFLAG(IS_ANDROID)
#if BUILDFLAG(ENABLE_HLS_DEMUXER) || BUILDFLAG(IS_ANDROID)

// These values are persisted to logs. Entries should not be renumbered and
// numeric values should never be reused.
Expand Down Expand Up @@ -87,7 +87,33 @@ MimeType TranslateMimeTypeToHistogramEnum(const base::StringPiece& mime_type) {
return MimeType::kOtherMimeType;
}

HlsFallbackImplementation SelectHlsFallbackImplementation() {
#if !BUILDFLAG(IS_ANDROID)
// TODO(crbug/1266991): This should return kBuiltinHlsPlayer when we launch
// on non-mobile. For now, do not support it.
return HlsFallbackImplementation::kNone;
#elif !BUILDFLAG(ENABLE_HLS_DEMUXER)
// Android build supporting only media player.
if (base::FeatureList::IsEnabled(kHlsPlayer)) {
return HlsFallbackImplementation::kMediaPlayer;
}
return HlsFallbackImplementation::kNone;
#else
// Android build with both builtin & media player implementations.
// Prefer builtin if it is enabled.
if (base::FeatureList::IsEnabled(kBuiltInHlsPlayer)) {
// TODO(crbug/1266991): This should return kBuiltinHlsPlayer when the
// HlsDemuxer is able to be created.
return HlsFallbackImplementation::kNone;
}
if (base::FeatureList::IsEnabled(kHlsPlayer)) {
return HlsFallbackImplementation::kMediaPlayer;
}
return kNone;
#endif
}

#endif // BUILDFLAG(ENABLE_HLS_DEMUXER) || BUILDFLAG(IS_ANDROID)

#if BUILDFLAG(ENABLE_FFMPEG)
// Returns true if `url` represents (or is likely to) a local file.
Expand Down Expand Up @@ -142,29 +168,27 @@ void DemuxerManager::OnPipelineError(PipelineStatus error) {
return client_->OnError(std::move(error));
}

#if BUILDFLAG(IS_ANDROID)
if (error == DEMUXER_ERROR_DETECTED_HLS) {
#if BUILDFLAG(ENABLE_HLS_DEMUXER) || BUILDFLAG(IS_ANDROID)
bool can_play_hls =
SelectHlsFallbackImplementation() != HlsFallbackImplementation::kNone;
if (can_play_hls && error == DEMUXER_ERROR_DETECTED_HLS) {
PipelineStatus reset_status =
ResetAfterHlsDetected(client_->IsSecurityOriginCryptographic());
SelectHlsFallbackMechanism(client_->IsSecurityOriginCryptographic());
if (!reset_status.is_ok()) {
client_->OnError(std::move(reset_status).AddCause(std::move(error)));
return;
}
// We have to stop the pipeline and delete the demuxer thread dumper pronto.
client_->StopForDemuxerReset();

// The data source must be stopped after the client.
// The data source must be stopped after the client, after which the
// old demuxer and data source can be freed.
client_->StopForDemuxerReset();
data_source_->Stop();

// Free the demuxer and data source now that they are stopped. After that,
// we can try restarting the client. If the client gets deleted in the mean
// time, we can stop there.
FreeResourcesAfterMediaThreadWait(base::BindOnce(
&DemuxerManager::RestartClientForHLS, weak_factory_.GetWeakPtr()));

return;
}
#endif
#endif // BUILDFLAG(ENABLE_HLS_DEMUXER) || BUILDFLAG(IS_ANDROID)

client_->OnError(std::move(error));
}
Expand Down Expand Up @@ -196,10 +220,43 @@ void DemuxerManager::SetLoadedUrl(GURL url) {
loaded_url_ = std::move(url);
}

PipelineStatus DemuxerManager::ResetAfterHlsDetected(bool cryptographic_url) {
#if BUILDFLAG(IS_ANDROID)
// If HLS isn't enabled, HLS detection should be the error.
if (!base::FeatureList::IsEnabled(kHlsPlayer)) {
#if BUILDFLAG(ENABLE_HLS_DEMUXER) || BUILDFLAG(IS_ANDROID)

void DemuxerManager::PopulateHlsHistograms(bool cryptographic_url) {
DCHECK(data_source_);

if (auto* co_data_source = data_source_->GetAsCrossOriginDataSource()) {
MimeType mime_type =
TranslateMimeTypeToHistogramEnum(co_data_source->GetMimeType());
base::UmaHistogramEnumeration("Media.WebMediaPlayerImpl.HLS.MimeType",
mime_type);

bool is_cross_origin = co_data_source->IsCorsCrossOrigin();
UMA_HISTOGRAM_BOOLEAN("Media.WebMediaPlayerImpl.HLS.IsCorsCrossOrigin",
is_cross_origin);
if (is_cross_origin) {
UMA_HISTOGRAM_BOOLEAN("Media.WebMediaPlayerImpl.HLS.HasAccessControl",
co_data_source->HasAccessControl());
base::UmaHistogramEnumeration(
"Media.WebMediaPlayerImpl.HLS.CorsCrossOrigin.MimeType", mime_type);
}
} else {
UMA_HISTOGRAM_BOOLEAN("Media.WebMediaPlayerImpl.HLS.IsCorsCrossOrigin",
false);
}

bool is_mixed_content =
cryptographic_url &&
(!loaded_url_.SchemeIsCryptographic() ||
GetDataSourceUrlAfterRedirects()->SchemeIsCryptographic());
UMA_HISTOGRAM_BOOLEAN("Media.WebMediaPlayerImpl.HLS.IsMixedContent",
is_mixed_content);
}

PipelineStatus DemuxerManager::SelectHlsFallbackMechanism(
bool cryptographic_url) {
hls_fallback_ = SelectHlsFallbackImplementation();
if (hls_fallback_ == HlsFallbackImplementation::kNone) {
return DEMUXER_ERROR_DETECTED_HLS;
}

Expand All @@ -212,46 +269,24 @@ PipelineStatus DemuxerManager::ResetAfterHlsDetected(bool cryptographic_url) {
// android's HLS implementation. Since HLS is enabled, we should report a
// failed external renderer, since we know MediaPlayerRenderer would fail
// anyway here.
// TODO(crbug/1266991): Consider allowing data:// URLs for the native HLS
// implementation.
const auto* co_data_source = data_source_->GetAsCrossOriginDataSource();
if (!co_data_source) {
bool is_mp = hls_fallback_ == HlsFallbackImplementation::kMediaPlayer;
if (!data_source_->GetAsCrossOriginDataSource() && is_mp) {
// Media player requires that the data source not be a data:// url.
return PIPELINE_ERROR_EXTERNAL_RENDERER_FAILED;
}

// Record the fallback.
bool manifest_url_is_cryptographic =
loaded_url_.SchemeIsCryptographic() &&
GetDataSourceUrlAfterRedirects()->SchemeIsCryptographic();
MimeType mime_type =
TranslateMimeTypeToHistogramEnum(co_data_source->GetMimeType());
bool is_cross_origin = co_data_source->IsCorsCrossOrigin();
base::UmaHistogramEnumeration("Media.WebMediaPlayerImpl.HLS.MimeType",
mime_type);
UMA_HISTOGRAM_BOOLEAN("Media.WebMediaPlayerImpl.HLS.IsCorsCrossOrigin",
is_cross_origin);
UMA_HISTOGRAM_BOOLEAN("Media.WebMediaPlayerImpl.HLS.IsMixedContent",
cryptographic_url && !manifest_url_is_cryptographic);
if (is_cross_origin) {
UMA_HISTOGRAM_BOOLEAN("Media.WebMediaPlayerImpl.HLS.HasAccessControl",
co_data_source->HasAccessControl());
base::UmaHistogramEnumeration(
"Media.WebMediaPlayerImpl.HLS.CorsCrossOrigin.MimeType", mime_type);
}

// Can't fail anymore, so set hls flag to true for next time we create a
// new demuxer.
demuxer_found_hls_ = true;
PopulateHlsHistograms(cryptographic_url);
loaded_url_ = GetDataSourceUrlAfterRedirects().value();

if (client_) {
client_->UpdateLoadedUrl(loaded_url_);
}

return OkStatus();
#else
return DEMUXER_ERROR_DETECTED_HLS;
#endif // BUILDFLAG(IS_ANDROID)
}

#endif // BUILDFLAG(ENABLE_HLS_DEMUXER) || BUILDFLAG(IS_ANDROID)

absl::optional<double> DemuxerManager::GetDemuxerDuration() {
if (!demuxer_) {
return absl::nullopt;
Expand Down Expand Up @@ -324,15 +359,25 @@ PipelineStatus DemuxerManager::CreateDemuxer(
}

#if BUILDFLAG(IS_ANDROID)
if (demuxer_found_hls_ || client_->IsMediaPlayerRendererClient()) {
SetDemuxer(CreateMediaUrlDemuxer(demuxer_found_hls_));
const bool media_player_hls =
hls_fallback_ == HlsFallbackImplementation::kMediaPlayer;
if (media_player_hls || client_->IsMediaPlayerRendererClient()) {
SetDemuxer(CreateMediaUrlDemuxer(media_player_hls));
return std::move(on_demuxer_created)
.Run(demuxer_.get(), Pipeline::StartType::kNormal,
/*is_streaming = */ false,
/*is_static = */ false);
}
#endif

#if BUILDFLAG(ENABLE_HLS_DEMUXER)
if (hls_fallback_ == HlsFallbackImplementation::kBuiltinHlsPlayer) {
// TODO(crbug/1266991) Implement the CreateHlsDemuxer method.
NOTREACHED();
return DEMUXER_ERROR_COULD_NOT_OPEN;
}
#endif // BUILDFLAG(ENABLE_HLS_DEMUXER)

// TODO(sandersd): FileSystem objects may also be non-static, but due to our
// caching layer such situations are broken already. http://crbug.com/593159
bool is_static = true;
Expand Down Expand Up @@ -423,9 +468,11 @@ void DemuxerManager::OnDataSourcePlaybackRateChange(double rate, bool paused) {
}

bool DemuxerManager::WouldTaintOrigin() const {
if (demuxer_found_hls_) {
if (hls_fallback_ != HlsFallbackImplementation::kNone) {
// HLS manifests might pull segments from a different origin. We can't know
// for sure, so we conservatively say yes here.
// TODO (crbug/1266991) We will be able to know for sure with the builtin
// player, when that's implemented.
return true;
}

Expand Down
15 changes: 12 additions & 3 deletions media/filters/demuxer_manager.h
Expand Up @@ -26,6 +26,12 @@

namespace media {

enum class HlsFallbackImplementation {
kNone,
kMediaPlayer,
kBuiltinHlsPlayer,
};

// This class manages both an implementation of media::Demuxer and of
// media::DataSource. DataSource, in particular may be null, since both MSE
// playback and Android's MediaPlayerRenderer do not make use of it. In the
Expand Down Expand Up @@ -60,7 +66,7 @@ class MEDIA_EXPORT DemuxerManager {

// Used for controlling the client when a demuxer swap happens.
virtual void StopForDemuxerReset() = 0;
virtual bool RestartForHls() = 0;
virtual void RestartForHls() = 0;

virtual bool IsSecurityOriginCryptographic() const = 0;

Expand Down Expand Up @@ -109,7 +115,10 @@ class MEDIA_EXPORT DemuxerManager {

void OnPipelineError(PipelineStatus error);
void SetLoadedUrl(GURL url);
PipelineStatus ResetAfterHlsDetected(bool cryptographic_url);
#if BUILDFLAG(ENABLE_HLS_DEMUXER) || BUILDFLAG(IS_ANDROID)
void PopulateHlsHistograms(bool cryptographic_url);
PipelineStatus SelectHlsFallbackMechanism(bool cryptographic_url);
#endif // BUILDFLAG(ENABLE_HLS_DEMUXER) || BUILDFLAG(IS_ANDROID)
void DisallowFallback();

// Methods that help manage demuxers
Expand Down Expand Up @@ -220,7 +229,7 @@ class MEDIA_EXPORT DemuxerManager {
bool allow_media_player_renderer_credentials_ = false;
#endif // BUILDFLAG(IS_ANDROID)

bool demuxer_found_hls_ = false;
HlsFallbackImplementation hls_fallback_ = HlsFallbackImplementation::kNone;

// Are we allowed to switch demuxer mid-stream when fallback error codes
// are encountered
Expand Down
27 changes: 17 additions & 10 deletions third_party/blink/renderer/platform/media/web_media_player_impl.cc
Expand Up @@ -517,7 +517,7 @@ WebMediaPlayerImpl::WebMediaPlayerImpl(
renderer_factory_selector_->SetRemotePlayStateChangeCB(
base::BindPostTaskToCurrentDefault(base::BindRepeating(
&WebMediaPlayerImpl::OnRemotePlayStateChange, weak_this_)));
#endif // defined (OS_ANDROID)
#endif // defined (IS_ANDROID)
}

WebMediaPlayerImpl::~WebMediaPlayerImpl() {
Expand Down Expand Up @@ -1807,20 +1807,26 @@ void WebMediaPlayerImpl::UpdateLoadedUrl(const GURL& url) {
loaded_url_ = url;
}

bool WebMediaPlayerImpl::RestartForHls() {
void WebMediaPlayerImpl::RestartForHls() {
DCHECK(main_task_runner_->BelongsToCurrentThread());

observer_->OnHlsManifestDetected();
#if BUILDFLAG(IS_ANDROID)
// TODO: DCHECK that |pipeline_| is stopped.

// Use the media player renderer if the native hls demuxer isn't compiled in
// or if the feature is disabled.
#if BUILDFLAG(ENABLE_HLS_DEMUXER)
if (!base::FeatureList::IsEnabled(media::kBuiltInHlsPlayer)) {
renderer_factory_selector_->SetBaseRendererType(
media::RendererType::kMediaPlayer);
}
#elif BUILDFLAG(IS_ANDROID)
renderer_factory_selector_->SetBaseRendererType(
media::RendererType::kMediaPlayer);
SetMemoryReportingState(false);
StartPipeline();
return true;
#else
return false;
// Shouldn't be reachable from desktop where hls is not enabled.
NOTREACHED();
#endif
SetMemoryReportingState(false);
StartPipeline();
}

void WebMediaPlayerImpl::OnError(media::PipelineStatus status) {
Expand Down Expand Up @@ -3741,7 +3747,8 @@ void WebMediaPlayerImpl::WriteSplitHistogram(

void WebMediaPlayerImpl::RecordUnderflowDuration(base::TimeDelta duration) {
DCHECK(demuxer_manager_->HasDataSource() ||
GetDemuxerType() == media::DemuxerType::kChunkDemuxer);
GetDemuxerType() == media::DemuxerType::kChunkDemuxer ||
GetDemuxerType() == media::DemuxerType::kHlsDemuxer);
WriteSplitHistogram<kPlaybackType | kEncrypted>(
&base::UmaHistogramTimes, "Media.UnderflowDuration2", duration);
}
Expand Down
Expand Up @@ -419,7 +419,7 @@ class PLATFORM_EXPORT WebMediaPlayerImpl
bool CouldPlayIfEnoughData() override;
bool IsMediaPlayerRendererClient() override;
void StopForDemuxerReset() override;
bool RestartForHls() override;
void RestartForHls() override;
bool IsSecurityOriginCryptographic() const override;
void UpdateLoadedUrl(const GURL& url) override;

Expand Down

0 comments on commit ec47185

Please sign in to comment.