Skip to content

(Draft) Backport fixes for player suspend#1682

Open
vivienne-w wants to merge 4 commits into
WebPlatformForEmbedded:wpe-2.46from
vivienne-w:wpe-2.46-player-suspend
Open

(Draft) Backport fixes for player suspend#1682
vivienne-w wants to merge 4 commits into
WebPlatformForEmbedded:wpe-2.46from
vivienne-w:wpe-2.46-player-suspend

Conversation

@vivienne-w
Copy link
Copy Markdown

@vivienne-w vivienne-w commented Jun 3, 2026

This backports my fixes for player suspends, plus a prerequisite patch we dont yet have in 2.46
6fb77b7

Build-Tests Layout-Tests
✅ 🛠 wpe-246-amd64-build ❌ 🧪 wpe-246-amd64-layout
✅ 🛠 wpe-246-arm32-build ❌ 🧪 wpe-246-arm32-layout

@vivienne-w vivienne-w added upstream Related to an upstream bug (or should be at some point) wpe-2.46 labels Jun 3, 2026
@vivienne-w vivienne-w force-pushed the wpe-2.46-player-suspend branch 2 times, most recently from c44060e to b6c49e1 Compare June 4, 2026 07:29
ntrrgc and others added 4 commits June 4, 2026 09:38
…ertion crash

https://bugs.webkit.org/show_bug.cgi?id=285850

Reviewed by Philippe Normand.

WebKit pauses muted videos when scrolling to save resources. However,
when running in Debug, an assertion was failing inside
MediaPlayerPrivateGStreamer::paused() in the GStreamer ports:

> ASSERTION FAILED: pipeline and player states are not synchronized

After some debugging, I found two problems that were causing the
assertion to fail:

(1) MediaPlayerPrivateGStreamerMSE::updateStates() didn't account for
this behavior, which could trigger unexpected state changes. The patch
adds it to the `shouldBePlaying` check.

(2) The assertion checks m_isPipelinePlaying against the actual state of
the pipeline. However, the code setting the pipeline to PAUSED when
scrolling away (see setVisibleInViewport()) didn't update this field.
Normally you use setPipelineState() instead which updates this field.

As drive-by fixes this patch also adds new logs and renames two fields
to have more useful names.

 * m_isVisibleInViewport is negated and renamed m_isPausedByViewport,
   reflecting its actual meaning (`m_isVisibleInViewport` was often true
   while the HTMLMediaElement was not visible in the viewport.

 * m_isVisible is renamed to m_pageIsVisible to both match other code and
   to reflect its actual meaning.

This patch adds back media-source-muted-scroll-and-seek-crash.html from
276798@main, which can be used to test the crash does not happen.  Note
however the same caveat from back then applies:

> Currently, part of the code to trigger the crash isn't executed
> due to WEBKIT_GST_ALLOW_PLAYBACK_OF_INVISIBLE_VIDEOS=1 being set in the test driver in
> Tools/Scripts/webkitpy/port/glib.py (264017@main), which needs to be commented manually.
> We should find a way to add a test preference for this code path to be enabled.

* LayoutTests/media/media-source/media-source-muted-scroll-and-seek-crash.html: Added.
* Source/WebCore/platform/graphics/gstreamer/MediaPlayerPrivateGStreamer.cpp:
(WebCore::MediaPlayerPrivateGStreamer::paused const):
(WebCore::MediaPlayerPrivateGStreamer::changePipelineState):
(WebCore::MediaPlayerPrivateGStreamer::setVisibleInViewport):
(WebCore::MediaPlayerPrivateGStreamer::paint):
* Source/WebCore/platform/graphics/gstreamer/MediaPlayerPrivateGStreamer.h:
* Source/WebCore/platform/graphics/gstreamer/mse/MediaPlayerPrivateGStreamerMSE.cpp:
(WebCore::MediaPlayerPrivateGStreamerMSE::updateStates):

Canonical link: https://commits.webkit.org/288906@main
…ewport

https://bugs.webkit.org/show_bug.cgi?id=287162

Reviewed by Philippe Normand.

This is a follow-up of the cleanups discussed in
WebKit/WebKit#39765.

This patch combines m_isPausedByViewport and m_invisiblePlayerState into
one single field and renames it to m_stateToRestoreWhenVisible. The
field is set to VOID_PENDING when we're not paused by viewport.

Additionally, safeguards have been added to setVisibleInViewport() to
prevent the suspension code from accidentally running more than once.

* Source/WebCore/platform/graphics/gstreamer/MediaPlayerPrivateGStreamer.cpp:
(WebCore::MediaPlayerPrivateGStreamer::changePipelineState):
(WebCore::MediaPlayerPrivateGStreamer::setVisibleInViewport):
(WebCore::MediaPlayerPrivateGStreamer::paint):
* Source/WebCore/platform/graphics/gstreamer/MediaPlayerPrivateGStreamer.h:
(WebCore::MediaPlayerPrivateGStreamer::isPausedByViewport):
* Source/WebCore/platform/graphics/gstreamer/mse/MediaPlayerPrivateGStreamerMSE.cpp:
(WebCore::MediaPlayerPrivateGStreamerMSE::updateStates):

Canonical link: https://commits.webkit.org/289997@main
https://bugs.webkit.org/show_bug.cgi?id=315755

Reviewed by NOBODY (OOPS!).

When the player is both muted and invisible, we try to suspend it (by just pausing the playback pipeline),
to save on resources. However so far this is happens quite inconsistently, with multiple issues:

First, suspends are tied too much to visibility changes - (un)muting can't suspend/resume,
so this refactors them out into `managePlayerSuspend()`, and also renames the relevant variables/methods
to reflect that they're about suspends, not player visibility.

Second, we are currently not able to suspend the player if it's already muted and hidden at creation:
Either we ignore it because we don't have a pipeline yet, or because it's in NULL.
In this scenario, we would also not yet have a target state to resume to, until e.g. a play() request later on,
so we need reintroduce a separate boolean flag (m_isSuspended) to make sure we know to delay that state change until we resume.

Finally, `m_isMuted` is currently updated inconsistently, and in some scenarios only from a notify::mute callback.
This presents a race condition, if we try to suspend after muting the player, but read m_isMuted before the callback
updates it. To avoid this, `isMuted()` now directly checks with the pipeline so we have a single source of truth.
Only `notifyPlayerOfMute()` still uses the flag to avoid sending redundant notifications.

* Source/WebCore/platform/graphics/gstreamer/MediaPlayerPrivateGStreamer.cpp:
(WebCore::MediaPlayerPrivateGStreamer::play):
(WebCore::MediaPlayerPrivateGStreamer::changePipelineState): Clear saved state if a change < PLAYING is requested; so a sequence like (suspend -> play() -> pause() -> unsuspend) doesn't end up in the wrong state.
(WebCore::MediaPlayerPrivateGStreamer::isMuted const):
(WebCore::MediaPlayerPrivateGStreamer::setMuted): Also check here if we need to suspend.
(WebCore::MediaPlayerPrivateGStreamer::createGSTPlayBin): After creating the pipeline, also check here if we need to suspend.
(WebCore::MediaPlayerPrivateGStreamer::setViewportVisibility):
(WebCore::MediaPlayerPrivateGStreamer::managePlayerSuspend): Added. Handles suspending/resuming the pipeline when needed.
(WebCore::MediaPlayerPrivateGStreamer::paint):
* Source/WebCore/platform/graphics/gstreamer/MediaPlayerPrivateGStreamer.h:
(WebCore::MediaPlayerPrivateGStreamer::isSuspended const):
(WebCore::MediaPlayerPrivateGStreamer::isPausedByViewport const): Deleted.
* Source/WebCore/platform/graphics/gstreamer/mse/MediaPlayerPrivateGStreamerMSE.cpp:
(WebCore::MediaPlayerPrivateGStreamerMSE::updateStates): Go forward with the state change even when suspended; changePipelineState() needs to save the new target state
@vivienne-w vivienne-w force-pushed the wpe-2.46-player-suspend branch from b6c49e1 to 6fb77b7 Compare June 4, 2026 07:41
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

upstream Related to an upstream bug (or should be at some point) wpe-2.46

Development

Successfully merging this pull request may close these issues.

2 participants