Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
12 changes: 12 additions & 0 deletions LayoutTests/media/restore-from-page-cache-paused-expected.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@

Test that a <video> that was paused before entering the back/forward cache is still paused, and still visibly displaying its last frame, after being restored. The visible-frame part of this is verified by the pixel/screenshot comparison against restore-from-page-cache-paused-expected.png, not by anything in this script.

EVENT(loadeddata)
EXPECTED (loadCount == '0') OK
EXPECTED (video.paused == 'true') OK
*** Page going into cache while paused
*** Page going into cache, video.paused = true
*** Page returned from cache
EXPECTED (loadCount == '1') OK
EXPECTED (video.paused == 'true') OK

73 changes: 73 additions & 0 deletions LayoutTests/media/restore-from-page-cache-paused.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
<!-- webkit-test-runner [ UsesBackForwardCache=true ] -->
<html>
<head>
<script src=media-file.js></script>
<script src=video-test.js></script>
<script>
var mediaFile = "content/counting.mp4";
var loadCount = 0;

if (window.testRunner) {
testRunner.dumpAsText(true);
testRunner.waitUntilDone();
}

function pageshow(evt)
{
if (!evt.persisted) {
// this is the first time the page has been loaded, setup the video element
setup();
return;
}

consoleWrite("*** Page returned from cache");
testExpected("loadCount", 1);

video = document.getElementsByTagName("video")[0];
testExpected("video.paused", true);

// Give the restored compositing layer a moment to settle before the harness
// forces a repaint and takes its snapshot.
requestAnimationFrame(function() {
if (window.testRunner)
testRunner.notifyDone();
});
}

function pagehide()
{
consoleWrite("*** Page going into cache, video.paused = " + video.paused);
}

function loadeddata()
{
testExpected("loadCount", 0);
testExpected("video.paused", true);

if (!loadCount) {
consoleWrite("*** Page going into cache while paused");
setTimeout('window.location = "data:text/html,<script>history.back()<" + "/script>"', 0);
}
++loadCount;
}

function setup()
{
video = document.getElementsByTagName("video")[0];
waitForEvent("loadeddata", loadeddata);
video.src = mediaFile;
}

window.onpageshow = pageshow;
window.onpagehide = pagehide;
</script>
</head>
<body>

<video></video>
<p>Test that a &lt;video&gt; that was paused before entering the back/forward cache is
still paused, and still visibly displaying its last frame, after being restored. The
visible-frame part of this is verified by the pixel/screenshot comparison against
restore-from-page-cache-paused-expected.png, not by anything in this script.</p>
</body>
</html>
13 changes: 13 additions & 0 deletions LayoutTests/media/restore-from-page-cache-playing-expected.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@

Test that a <video> that was playing before entering the back/forward cache is still playing (and currentTime is advancing again) after being restored.

EVENT(canplaythrough)
EXPECTED (loadCount == '0') OK
EVENT(playing)
*** Page going into cache while playing
*** Page going into cache, video.paused = false
*** Page returned from cache
EXPECTED (loadCount == '1') OK
EXPECTED (video.paused == 'false') OK
EXPECTED (video.currentTime > timeBeforeCache == 'true') OK

85 changes: 85 additions & 0 deletions LayoutTests/media/restore-from-page-cache-playing.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
<!-- webkit-test-runner [ UsesBackForwardCache=true ] -->
<html>
<head>
<script src=media-file.js></script>
<script src=video-test.js></script>
<script>
var mediaFile = findMediaFile("video", "content/test");
var loadCount = 0;
var timeBeforeCache;

if (window.testRunner) {
testRunner.dumpAsText();
testRunner.waitUntilDone();
}

function waitForTimeToAdvance() {
if (video.currentTime > timeBeforeCache) {
testExpected("video.currentTime > timeBeforeCache", true);
if (window.testRunner)
testRunner.notifyDone();
return;
}
requestAnimationFrame(waitForTimeToAdvance);
}

function pageshow(evt)
{
if (!evt.persisted) {
// this is the first time the page has been loaded, setup the video element
setup();
return;
}

consoleWrite("*** Page returned from cache");
testExpected("loadCount", 1);

video = document.getElementsByTagName("video")[0];
testExpected("video.paused", false);

waitForTimeToAdvance();
}

function pagehide()
{
timeBeforeCache = video.currentTime;
consoleWrite("*** Page going into cache, video.paused = " + video.paused);
}

function canplaythrough()
{
testExpected("loadCount", 0);

if (!loadCount) {
// Wait for the 'playing' event (not just calling .play()) before caching, so
// the pipeline has actually reached the PLAYING state (not just requested it)
// matching a genuinely-playing video rather than one that was paused
// again before playback really started.
waitForEvent("playing", function() {
consoleWrite("*** Page going into cache while playing");
var blob = new Blob(['<!DOCTYPE html><script>setTimeout(function() { history.back(); }, 0);<\/script>'], { type: "text/html" });
window.location.href = URL.createObjectURL(blob);
}, false, true);
video.play();
}
++loadCount;
}

function setup()
{
video = document.getElementsByTagName("video")[0];
waitForEvent("canplaythrough", canplaythrough);
video.src = mediaFile;
}

window.onpageshow = pageshow;
window.onpagehide = pagehide;
</script>
</head>
<body>

<video></video>
<p>Test that a &lt;video&gt; that was playing before entering the back/forward cache
is still playing (and currentTime is advancing again) after being restored.</p>
</body>
</html>
1 change: 1 addition & 0 deletions LayoutTests/platform/ios/TestExpectations
Original file line number Diff line number Diff line change
Expand Up @@ -2515,6 +2515,7 @@ media/media-can-play-webm.html [ Failure ]
media/media-higher-prio-audio-stream.html [ Failure ]
media/progress-events-generated-correctly.html [ Failure ]
media/restore-from-page-cache.html [ Failure ]
media/restore-from-page-cache-playing.html [ Failure ]
media/track/track-forced-subtitles-in-band.html [ Failure ]
media/track/track-in-band-mode.html [ Pass Failure ]
media/track/w3c/interfaces/TextTrackCue/align.html [ Failure ]
Expand Down
23 changes: 17 additions & 6 deletions Source/WebCore/html/HTMLMediaElement.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5919,16 +5919,17 @@ void HTMLMediaElement::cancelPendingTasks()
#endif
}

void HTMLMediaElement::userCancelledLoad()
void HTMLMediaElement::userCancelledLoad(ShouldDestroyMediaPlayer shouldDestroyMediaPlayer)
{
INFO_LOG(LOGIDENTIFIER);

// FIXME: We should look to reconcile the iOS and non-iOS code (below).
#if PLATFORM(IOS_FAMILY)
UNUSED_PARAM(shouldDestroyMediaPlayer);
if (m_networkState == NETWORK_EMPTY || m_readyState >= HAVE_METADATA)
return;
#else
if (m_networkState == NETWORK_EMPTY || m_completelyLoaded)
if (m_networkState == NETWORK_EMPTY || m_completelyLoaded || shouldDestroyMediaPlayer == ShouldDestroyMediaPlayer::No)
return;
#endif

Expand Down Expand Up @@ -6060,7 +6061,7 @@ void HTMLMediaElement::stopWithoutDestroyingMediaPlayer()

setAutoplayEventPlaybackState(AutoplayEventPlaybackState::None);

userCancelledLoad();
userCancelledLoad(ShouldDestroyMediaPlayer::No);

updateRenderer();

Expand Down Expand Up @@ -6113,8 +6114,10 @@ void HTMLMediaElement::suspend(ReasonForSuspension reason)
case ReasonForSuspension::BackForwardCache:
stopWithoutDestroyingMediaPlayer();
setBufferingPolicy(BufferingPolicy::MakeResourcesPurgeable);
if (m_mediaSession)
if (m_mediaSession) {
m_mediaSession->addBehaviorRestriction(MediaElementSession::RequirePageConsentToResumeMedia);
m_mediaSession->mediaUsageManagerSessionWillBeSuspended();
}
break;
case ReasonForSuspension::PageWillBeSuspended:
stopWithoutDestroyingMediaPlayer();
Expand Down Expand Up @@ -8116,8 +8119,16 @@ void HTMLMediaElement::resumeAutoplaying()
void HTMLMediaElement::mayResumePlayback(bool shouldResume)
{
ALWAYS_LOG(LOGIDENTIFIER, "paused = ", paused());
if (paused() && shouldResume)
play();
if (ended())
return;

if (paused()) {
if (shouldResume)
play();
return;
}

updatePlayState();
}

String HTMLMediaElement::mediaSessionTitle() const
Expand Down
3 changes: 2 additions & 1 deletion Source/WebCore/html/HTMLMediaElement.h
Original file line number Diff line number Diff line change
Expand Up @@ -821,7 +821,8 @@ class HTMLMediaElement
void loadResource(const URL&, ContentType&, const String& keySystem);
void scheduleNextSourceChild();
void loadNextSourceChild();
void userCancelledLoad();
enum class ShouldDestroyMediaPlayer : bool { No, Yes };
void userCancelledLoad(ShouldDestroyMediaPlayer = ShouldDestroyMediaPlayer::Yes);
void clearMediaPlayer();
bool havePotentialSourceChild();
void noneSupported();
Expand Down
12 changes: 12 additions & 0 deletions Source/WebCore/html/MediaElementSession.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,18 @@ void MediaElementSession::addMediaUsageManagerSessionIfNecessary()
#endif
}

void MediaElementSession::mediaUsageManagerSessionWillBeSuspended()
{
#if ENABLE(MEDIA_USAGE)
// The back/forward cache keeps this MediaElementSession (and thus this flag) alive across
// suspend/resume, but the UI process unconditionally clears its usage-tracking map on every
// navigation commit, including the eventual restore commit.
// Reset the flag so the next updateMediaUsageIfChanged() after resuming re-adds the session
// before updating it, instead of sending an update for an identifier the UI process no longer has.
m_haveAddedMediaUsageManagerSession = false;
#endif
}

void MediaElementSession::registerWithDocument(Document& document)
{
#if ENABLE(WIRELESS_PLAYBACK_TARGET)
Expand Down
2 changes: 2 additions & 0 deletions Source/WebCore/html/MediaElementSession.h
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,8 @@ class MediaElementSession final : public PlatformMediaSession {
WEBCORE_EXPORT void removeBehaviorRestriction(BehaviorRestrictions);
bool hasBehaviorRestriction(BehaviorRestrictions restriction) const { return restriction & m_restrictions; }

void mediaUsageManagerSessionWillBeSuspended();

#if ENABLE(MEDIA_SOURCE)
size_t maximumMediaSourceBufferSize(const SourceBuffer&) const;
#endif
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -176,7 +176,7 @@ MediaPlayerPrivateGStreamer::MediaPlayerPrivateGStreamer(MediaPlayer* player)
, m_drawTimer(RunLoop::main(), this, &MediaPlayerPrivateGStreamer::repaint)
, m_pausedTimerHandler(RunLoop::main(), this, &MediaPlayerPrivateGStreamer::pausedTimerFired)
#if USE(TEXTURE_MAPPER_GL) && !USE(NICOSIA)
, m_platformLayerProxy(adoptRef(new TextureMapperPlatformLayerProxyGL))
, m_platformLayerProxy(adoptRef(new TextureMapperPlatformLayerProxyGL([this] { pushTextureToCompositor(); })))
#endif
#if !RELEASE_LOG_DISABLED
, m_logger(player->mediaPlayerLogger())
Expand Down Expand Up @@ -204,9 +204,13 @@ MediaPlayerPrivateGStreamer::MediaPlayerPrivateGStreamer(MediaPlayer* player)

#if USE(TEXTURE_MAPPER_DMABUF)
if (webKitDMABufVideoSinkIsEnabled() && webKitDMABufVideoSinkProbePlatform())
return adoptRef(*new TextureMapperPlatformLayerProxyDMABuf);
return adoptRef(*new TextureMapperPlatformLayerProxyDMABuf([this] {
pushTextureToCompositor();
}));
#endif
return adoptRef(*new TextureMapperPlatformLayerProxyGL);
return adoptRef(*new TextureMapperPlatformLayerProxyGL([this] {
pushTextureToCompositor();
}));
}()));
#endif

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,14 @@
namespace WebCore {

TextureMapperPlatformLayerProxy::TextureMapperPlatformLayerProxy() = default;

#if ENABLE(VIDEO) && USE(GSTREAMER)
TextureMapperPlatformLayerProxy::TextureMapperPlatformLayerProxy(Function<void()>&& layerAttachedCallback)
: m_layerAttachedCallback(WTFMove(layerAttachedCallback))
{
}
#endif

TextureMapperPlatformLayerProxy::~TextureMapperPlatformLayerProxy() = default;

bool TextureMapperPlatformLayerProxy::isActive()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@

#if USE(COORDINATED_GRAPHICS)

#include <wtf/Function.h>
#include <wtf/Lock.h>
#include <wtf/ThreadSafeRefCounted.h>

Expand Down Expand Up @@ -60,9 +61,22 @@ class TextureMapperPlatformLayerProxy : public ThreadSafeRefCounted<TextureMappe
virtual void swapBuffer() = 0;

protected:
#if ENABLE(VIDEO) && USE(GSTREAMER)
// layerAttachedCallback is invoked on the compositing thread whenever a new (non-null)
// target layer is attached, e.g. because the previous layer was destroyed and recreated,
// which happens whenever the render tree is rebuilt, such as when a page is restored from
// the back/forward cache. This lets the owning media player re-deliver its current frame so
// the new layer isn't left blank until the next decoded sample arrives (which, for a paused
// player, may never happen).
TextureMapperPlatformLayerProxy(Function<void()>&& layerAttachedCallback);
#endif

Lock m_lock;
Compositor* m_compositor { nullptr };
TextureMapperLayer* m_targetLayer { nullptr };
#if ENABLE(VIDEO) && USE(GSTREAMER)
Function<void()> m_layerAttachedCallback;
#endif
};

} // namespace WebCore
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,13 @@ struct TextureMapperPlatformLayerProxyDMABuf::DMABufLayer::EGLImageData {
};

TextureMapperPlatformLayerProxyDMABuf::TextureMapperPlatformLayerProxyDMABuf() = default;
#if ENABLE(VIDEO) && USE(GSTREAMER)
TextureMapperPlatformLayerProxyDMABuf::TextureMapperPlatformLayerProxyDMABuf(Function<void()>&& layerAttachedCallback)
: TextureMapperPlatformLayerProxy(WTFMove(layerAttachedCallback))
{
}
#endif

TextureMapperPlatformLayerProxyDMABuf::~TextureMapperPlatformLayerProxyDMABuf() = default;

void TextureMapperPlatformLayerProxyDMABuf::activateOnCompositingThread(Compositor* compositor, TextureMapperLayer* targetLayer)
Expand All @@ -106,11 +113,21 @@ void TextureMapperPlatformLayerProxyDMABuf::activateOnCompositingThread(Composit
ASSERT(compositor);
ASSERT(targetLayer);

#if ENABLE(VIDEO) && USE(GSTREAMER)
bool didAttachNewLayer;
#endif
{
Locker locker { m_lock };
m_compositor = compositor;
#if ENABLE(VIDEO) && USE(GSTREAMER)
didAttachNewLayer = targetLayer && m_targetLayer != targetLayer;
#endif
m_targetLayer = targetLayer;
}
#if ENABLE(VIDEO) && USE(GSTREAMER)
if (didAttachNewLayer && m_layerAttachedCallback)
m_layerAttachedCallback();
#endif
}

void TextureMapperPlatformLayerProxyDMABuf::invalidate()
Expand Down
Loading