Commit 2dc6089a authored by Dale Curtis's avatar Dale Curtis Committed by Commit Bot

Add the ability to suspend preload=metadata playbacks in some cases.

This adds a base::Feature, kPreloadMetadataSuspend, for suspending
preload=metadata players that are either audio-only or have video
and have a poster image.

Given the uncertainty and issues with canplay, canplaythrough, the
current version of this feature signals HAVE_ENOUGH when after a
suspended startup successfull completes.

This also fixes a minor issue where streams with a positive start
time were not suspending at the right time.

BUG=694855
TEST=enabling feature still passes all bots. More layout tests,
which are disabled by default in this CL since the feature is
disabled by default for now.

Change-Id: Iccd79eddbd7a3b03933b3090eb99ad19c2fed768
Reviewed-on: https://chromium-review.googlesource.com/978867Reviewed-by: default avatarFrank Liberato <liberato@chromium.org>
Reviewed-by: default avatarDan Sanders <sandersd@chromium.org>
Commit-Queue: Dale Curtis <dalecurtis@chromium.org>
Cr-Commit-Position: refs/heads/master@{#545817}
parent ea68443e
......@@ -186,6 +186,9 @@ const base::Feature kOverlayFullscreenVideo{"overlay-fullscreen-video",
const base::Feature kPictureInPicture{"PictureInPicture",
base::FEATURE_DISABLED_BY_DEFAULT};
const base::Feature kPreloadMetadataSuspend{"PreloadMetadataSuspend",
base::FEATURE_DISABLED_BY_DEFAULT};
// Let videos be resumed via remote controls (for example, the notification)
// when in background.
const base::Feature kResumeBackgroundVideo {
......
......@@ -120,6 +120,7 @@ MEDIA_EXPORT extern const base::Feature kOverflowIconsForMediaControls;
MEDIA_EXPORT extern const base::Feature kOverlayFullscreenVideo;
MEDIA_EXPORT extern const base::Feature kPictureInPicture;
MEDIA_EXPORT extern const base::Feature kPreloadMediaEngagementData;
MEDIA_EXPORT extern const base::Feature kPreloadMetadataSuspend;
MEDIA_EXPORT extern const base::Feature kResumeBackgroundVideo;
MEDIA_EXPORT extern const base::Feature kSpecCompliantCanPlayThrough;
MEDIA_EXPORT extern const base::Feature kSupportExperimentalCdmInterface;
......
......@@ -1006,7 +1006,8 @@ void PipelineImpl::RendererWrapper::ReportMetadata(StartType start_type) {
// Abort pending render initialization tasks and suspend the pipeline.
pending_callbacks_.reset();
DestroyRenderer();
shared_state_.suspend_timestamp = base::TimeDelta();
shared_state_.suspend_timestamp =
std::max(base::TimeDelta(), demuxer_->GetStartTime());
SetState(kSuspended);
main_task_runner_->PostTask(
FROM_HERE, base::Bind(&PipelineImpl::OnSeekDone, weak_pipeline_, true));
......
......@@ -1370,6 +1370,26 @@ void WebMediaPlayerImpl::OnPipelineSeeked(bool time_updated) {
// Background video optimizations are delayed when shown/hidden if pipeline
// is seeking.
UpdateBackgroundVideoOptimizationState();
// If we successfully completed a suspended startup, lie about our buffering
// state for the time being. While ultimately we want to avoid lying about the
// buffering state, for the initial test of true preload=metadata, signal
// BUFFERING_HAVE_ENOUGH so that canplay and canplaythrough fire correctly.
//
// Later we can experiment with the impact of removing this lie; initial data
// suggests high disruption since we've also made preload=metadata the
// default. Most sites are not prepared for a lack of canplay; even many of
// our own tests don't function correctly. See https://crbug.com/694855.
//
// Note: This call is dual purpose, it is also responsible for triggering an
// UpdatePlayState() call which may need to resume the pipeline once Blink
// has been told about the ReadyState change.
if (attempting_suspended_start_ &&
pipeline_controller_.IsPipelineSuspended()) {
OnBufferingStateChangeInternal(BUFFERING_HAVE_ENOUGH, true);
}
attempting_suspended_start_ = false;
}
void WebMediaPlayerImpl::OnPipelineSuspended() {
......@@ -1567,6 +1587,10 @@ void WebMediaPlayerImpl::OnMetadata(PipelineMetadata metadata) {
UpdatePlayState();
}
void WebMediaPlayerImpl::OnBufferingStateChange(BufferingState state) {
OnBufferingStateChangeInternal(state, false);
}
void WebMediaPlayerImpl::CreateVideoDecodeStatsReporter() {
// TODO(chcunningham): destroy reporter if we initially have video but the
// track gets disabled. Currently not possible in default desktop Chrome.
......@@ -1641,13 +1665,14 @@ bool WebMediaPlayerImpl::CanPlayThrough() {
playback_rate_ == 0.0 ? 1.0 : playback_rate_);
}
void WebMediaPlayerImpl::OnBufferingStateChange(BufferingState state) {
void WebMediaPlayerImpl::OnBufferingStateChangeInternal(BufferingState state,
bool force_update) {
DVLOG(1) << __func__ << "(" << state << ")";
DCHECK(main_task_runner_->BelongsToCurrentThread());
// Ignore buffering state changes until we've completed all outstanding
// operations.
if (!pipeline_controller_.IsStable())
// operations unless we've been asked to force the update.
if (!pipeline_controller_.IsStable() && !force_update)
return;
media_log_->AddEvent(media_log_->CreateBufferingStateChangedEvent(
......@@ -2306,11 +2331,21 @@ void WebMediaPlayerImpl::StartPipeline() {
bool is_streaming = IsStreaming();
UMA_HISTOGRAM_BOOLEAN("Media.IsStreaming", is_streaming);
// If possible attempt to avoid decoder spool up until playback starts.
Pipeline::StartType start_type = Pipeline::StartType::kNormal;
if (base::FeatureList::IsEnabled(kPreloadMetadataSuspend) &&
!chunk_demuxer_ && preload_ == MultibufferDataSource::METADATA) {
start_type = has_poster_
? Pipeline::StartType::kSuspendAfterMetadata
: Pipeline::StartType::kSuspendAfterMetadataForAudioOnly;
attempting_suspended_start_ = true;
}
// ... and we're ready to go!
// TODO(sandersd): On Android, defer Start() if the tab is not visible.
seeking_ = true;
pipeline_controller_.Start(Pipeline::StartType::kNormal, demuxer_.get(), this,
is_streaming, is_static);
pipeline_controller_.Start(start_type, demuxer_.get(), this, is_streaming,
is_static);
}
void WebMediaPlayerImpl::SetNetworkState(WebMediaPlayer::NetworkState state) {
......
......@@ -532,6 +532,12 @@ class MEDIA_BLINK_EXPORT WebMediaPlayerImpl
// without buffering.
bool CanPlayThrough();
// Internal implementation of Pipeline::Client::OnBufferingStateChange(). When
// |force_update| is true, the given state will be set even if the pipeline is
// not currently stable.
void OnBufferingStateChangeInternal(BufferingState state,
bool force_update = false);
// Records |natural_size| to MediaLog and video height to UMA.
void RecordVideoNaturalSize(const gfx::Size& natural_size);
......@@ -876,6 +882,10 @@ class MEDIA_BLINK_EXPORT WebMediaPlayerImpl
base::Optional<bool> stale_state_override_for_testing_;
// True if we attempt to start the media pipeline in a suspended state for
// preload=metadata. Cleared upon pipeline startup.
bool attempting_suspended_start_ = false;
// Keeps track of the SurfaceId for Picture-in-Picture. This is used to
// route the video to be shown in the Picture-in-Picture window.
viz::SurfaceId pip_surface_id_;
......
......@@ -3027,6 +3027,12 @@ crbug.com/769056 fast/text/emoji-web-font.html [ Failure ]
# Paint-timing tests timeout without threaded-compositing, so skip them.
crbug.com/720047 external/wpt/paint-timing [ Skip ]
# This feature is disabled currently, these tests should be enabled once the
# feature has been enabled by default.
crbug.com/694855 media/audio-src-suspend-after-have-metadata.html [ Skip ]
crbug.com/694855 media/video-src-skip-suspend-after-have-metadata.html [ Skip ]
crbug.com/694855 media/video-src-suspend-after-have-metadata.html [ Skip ]
crbug.com/770232 [ Win10 ] editing/selection/paint-hyphen.html [ Failure ]
crbug.com/770232 [ Win10 ] fast/text/hyphenate-character.html [ Failure ]
crbug.com/770232 [ Win10 ] fast/text/unicode-fallback-font.html [ Failure ]
......
......@@ -20,26 +20,32 @@ function runTest (url, oncomplete, tester)
context = new OfflineAudioContext(1, sampleRate * lengthInSeconds, sampleRate);
audio = document.createElement('audio');
if (tester) {
tester();
} else {
audio.src = url;
}
audio.autoplay = true;
source = context.createMediaElementSource(audio);
source.connect(context.destination);
// Note: In practice this is not a reliable way to ensure the media element
// is ready to provide samples; unfortunately if the element is not ready
// yet, the offline context may produce silence in a spin-loop.
//
// With some knowledge of the internals we can make this test work by
// marking the element as autoplay above; this mostly ensures that the
// pipeline is ready to provide samples.
audio.addEventListener("playing", function(e) {
// If we receive multiple playing events, we still can't invoke
// startRendering multiple times.
context.startRendering().catch(() => {});
});
// If we receive multiple playing events, we still can't invoke
// startRendering multiple times.
context.startRendering().catch(() => {});
});
context.oncomplete = function(e) {
checkResult(e);
finishJSTest();
}
audio.play();
if (tester) {
tester();
} else {
audio.src = url;
}
}
<!DOCTYPE html>
<title>Verify that an audio element w/ preload=metadata is suspended by default and can be resumed properly.</title>
<script src="../resources/testharness.js"></script>
<script src="../resources/testharnessreport.js"></script>
<script src="suspend-util.js"></script>
<audio></audio>
<script>
async_test(function(t) {
preloadMetadataSuspendTest(
t, document.querySelector('audio'), 'content/silence.wav', true);
});
</script>
......@@ -12,6 +12,35 @@ function suspendMediaElement(video, callback) {
window.internals.forceStaleStateForMediaElement(video);
}
function preloadMetadataSuspendTest(t, video, src, expectSuspend) {
assert_true(!!window.internals, 'This test requires windows.internals.');
video.onerror = t.unreached_func();
var timeWatcher = t.step_func(function() {
if (video.currentTime > 0) {
assert_false(window.internals.isMediaElementSuspended(video));
t.done();
} else {
window.requestAnimationFrame(timeWatcher);
}
});
var eventListener = t.step_func(function() {
assert_equals(expectSuspend,
window.internals.isMediaElementSuspended(video));
if (!expectSuspend) {
t.done();
return;
}
window.requestAnimationFrame(timeWatcher);
video.play();
});
video.addEventListener('loadedmetadata', eventListener, false);
video.src = src;
}
function suspendTest(t, video, src, eventName, expectedState) {
assert_true(!!window.internals, 'This test requires windows.internals.');
video.onerror = t.unreached_func();
......
<!DOCTYPE html>
<title>Verify that a video element w/ preload=metadata and no poster is not suspended by default.</title>
<script src="../resources/testharness.js"></script>
<script src="../resources/testharnessreport.js"></script>
<script src="suspend-util.js"></script>
<video></video>
<script>
async_test(function(t) {
preloadMetadataSuspendTest(
t, document.querySelector('video'), 'content/test.webm', false);
});
</script>
<!DOCTYPE html>
<title>Verify that a video element w/ preload=metadata and a poster is suspended by default and can be resumed properly.</title>
<script src="../resources/testharness.js"></script>
<script src="../resources/testharnessreport.js"></script>
<script src="suspend-util.js"></script>
<video poster="content/abe.png"></video>
<script>
async_test(function(t) {
preloadMetadataSuspendTest(
t, document.querySelector('video'), 'content/test.webm', true);
});
</script>
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment