From 14bf12157a48e2b0ec47401567ba7e5db7fcce1d Mon Sep 17 00:00:00 2001 From: Filipe Norte Date: Wed, 6 Aug 2025 10:51:09 +0000 Subject: [PATCH 1/3] Fix buffering handling in progressive playback When performing a seek operation during playback, the pipeline switches to the PAUSED state and starts buffering data. As this transition is async, we may reach the 100% buffer level before the state change completes. This may cause updateBufferingStatus() to stop getting called while the async transition is still in progress. As m_isBuffering was getting set back to m_wasBuffering value in updateStates() async state handling, once the state change completed, the updateStates() might not detect that we are not buffering anymore, as m_isBuffering is holding the wrong value. --- .../gstreamer/MediaPlayerPrivateGStreamer.cpp | 23 ++++++++++--------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/Source/WebCore/platform/graphics/gstreamer/MediaPlayerPrivateGStreamer.cpp b/Source/WebCore/platform/graphics/gstreamer/MediaPlayerPrivateGStreamer.cpp index 1522ed79c0b5..e6442693caa8 100644 --- a/Source/WebCore/platform/graphics/gstreamer/MediaPlayerPrivateGStreamer.cpp +++ b/Source/WebCore/platform/graphics/gstreamer/MediaPlayerPrivateGStreamer.cpp @@ -2334,8 +2334,18 @@ void MediaPlayerPrivateGStreamer::updateMaxTimeLoaded(double percentage) void MediaPlayerPrivateGStreamer::updateBufferingStatus(GstBufferingMode mode, double percentage, bool resetHistory, bool shouldUpdateStates) { - m_wasBuffering = m_isBuffering; - m_previousBufferingPercentage = m_bufferingPercentage; + GstStateChangeReturn getStateResult = gst_element_get_state(m_pipeline.get(), nullptr, nullptr, 0); + + // If an async transition is in progress, we keep the previous buffering state and percentage so that the buffering + // state transition can be correctly detected once the state change completes, as updateBufferingStatus() might + // not get called again after reaching max level while we are still in the async transition state. + // This ensures consistency in case updateBufferingStatus() is called without calling updateStates(), as well as + // calls to updateStates() whithout prior calls to updateBufferingStatus(). + if (getStateResult != GST_STATE_CHANGE_ASYNC) + { + m_wasBuffering = m_isBuffering; + m_previousBufferingPercentage = m_bufferingPercentage; + } #ifndef GST_DISABLE_GST_DEBUG GUniquePtr modeString(g_enum_to_string(GST_TYPE_BUFFERING_MODE, mode)); @@ -2882,15 +2892,6 @@ void MediaPlayerPrivateGStreamer::updateStates() case GST_STATE_CHANGE_ASYNC: GST_DEBUG_OBJECT(pipeline(), "Async: State: %s, pending: %s", gst_element_state_get_name(m_currentState), gst_element_state_get_name(pending)); // Change in progress. - - // Delay the m_isBuffering change by returning it to its previous value. Without this, the false --> true change - // would go unnoticed by the code that should trigger a pause. - if (m_wasBuffering != m_isBuffering && !m_isPaused && m_playbackRate) { - GST_TRACE_OBJECT(pipeline(), "[Buffering] Delaying m_isBuffering %s --> %s to force the proper change from not buffering to buffering when the async state change completes.", boolForPrinting(m_wasBuffering), boolForPrinting(m_isBuffering)); - m_isBuffering = m_wasBuffering; - m_bufferingPercentage = m_previousBufferingPercentage; - } - break; case GST_STATE_CHANGE_FAILURE: GST_DEBUG_OBJECT(pipeline(), "Failure: State: %s, pending: %s", gst_element_state_get_name(m_currentState), gst_element_state_get_name(pending)); From 5040178847a27b4bf7d4e835bf2b5a8c46815558 Mon Sep 17 00:00:00 2001 From: Filipe Norte Date: Wed, 13 Aug 2025 10:03:37 +0000 Subject: [PATCH 2/3] Revert "Fix buffering handling in progressive playback" This reverts commit 14bf12157a48e2b0ec47401567ba7e5db7fcce1d. --- .../gstreamer/MediaPlayerPrivateGStreamer.cpp | 23 +++++++++---------- 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/Source/WebCore/platform/graphics/gstreamer/MediaPlayerPrivateGStreamer.cpp b/Source/WebCore/platform/graphics/gstreamer/MediaPlayerPrivateGStreamer.cpp index e6442693caa8..1522ed79c0b5 100644 --- a/Source/WebCore/platform/graphics/gstreamer/MediaPlayerPrivateGStreamer.cpp +++ b/Source/WebCore/platform/graphics/gstreamer/MediaPlayerPrivateGStreamer.cpp @@ -2334,18 +2334,8 @@ void MediaPlayerPrivateGStreamer::updateMaxTimeLoaded(double percentage) void MediaPlayerPrivateGStreamer::updateBufferingStatus(GstBufferingMode mode, double percentage, bool resetHistory, bool shouldUpdateStates) { - GstStateChangeReturn getStateResult = gst_element_get_state(m_pipeline.get(), nullptr, nullptr, 0); - - // If an async transition is in progress, we keep the previous buffering state and percentage so that the buffering - // state transition can be correctly detected once the state change completes, as updateBufferingStatus() might - // not get called again after reaching max level while we are still in the async transition state. - // This ensures consistency in case updateBufferingStatus() is called without calling updateStates(), as well as - // calls to updateStates() whithout prior calls to updateBufferingStatus(). - if (getStateResult != GST_STATE_CHANGE_ASYNC) - { - m_wasBuffering = m_isBuffering; - m_previousBufferingPercentage = m_bufferingPercentage; - } + m_wasBuffering = m_isBuffering; + m_previousBufferingPercentage = m_bufferingPercentage; #ifndef GST_DISABLE_GST_DEBUG GUniquePtr modeString(g_enum_to_string(GST_TYPE_BUFFERING_MODE, mode)); @@ -2892,6 +2882,15 @@ void MediaPlayerPrivateGStreamer::updateStates() case GST_STATE_CHANGE_ASYNC: GST_DEBUG_OBJECT(pipeline(), "Async: State: %s, pending: %s", gst_element_state_get_name(m_currentState), gst_element_state_get_name(pending)); // Change in progress. + + // Delay the m_isBuffering change by returning it to its previous value. Without this, the false --> true change + // would go unnoticed by the code that should trigger a pause. + if (m_wasBuffering != m_isBuffering && !m_isPaused && m_playbackRate) { + GST_TRACE_OBJECT(pipeline(), "[Buffering] Delaying m_isBuffering %s --> %s to force the proper change from not buffering to buffering when the async state change completes.", boolForPrinting(m_wasBuffering), boolForPrinting(m_isBuffering)); + m_isBuffering = m_wasBuffering; + m_bufferingPercentage = m_previousBufferingPercentage; + } + break; case GST_STATE_CHANGE_FAILURE: GST_DEBUG_OBJECT(pipeline(), "Failure: State: %s, pending: %s", gst_element_state_get_name(m_currentState), gst_element_state_get_name(pending)); From 4fcc021a4d3e473b02453e2076c8b5d63a46f09f Mon Sep 17 00:00:00 2001 From: Filipe Norte Date: Wed, 13 Aug 2025 10:04:45 +0000 Subject: [PATCH 3/3] Fix buffering handling in progressive playback When performing a seek operation during playback, the pipeline switches to the PAUSED state and starts buffering data. As this transition is async, we may reach the 100% buffer level before the state change completes. This may cause updateBufferingStatus() to stop getting called while the async transition is still in progress. As m_isBuffering was getting set back to m_wasBuffering value in updateStates() async state handling, once the state change completed, the updateStates() might not detect that we are not buffering anymore, as m_isBuffering is holding the wrong value. --- .../gstreamer/MediaPlayerPrivateGStreamer.cpp | 13 +++++++++++++ .../gstreamer/MediaPlayerPrivateGStreamer.h | 1 + 2 files changed, 14 insertions(+) diff --git a/Source/WebCore/platform/graphics/gstreamer/MediaPlayerPrivateGStreamer.cpp b/Source/WebCore/platform/graphics/gstreamer/MediaPlayerPrivateGStreamer.cpp index 1522ed79c0b5..4cf6a140f80f 100644 --- a/Source/WebCore/platform/graphics/gstreamer/MediaPlayerPrivateGStreamer.cpp +++ b/Source/WebCore/platform/graphics/gstreamer/MediaPlayerPrivateGStreamer.cpp @@ -2104,6 +2104,8 @@ void MediaPlayerPrivateGStreamer::handleMessage(GstMessage* message) if (!messageSourceIsPlaybin || m_isDelayingLoad) break; + updateBufferingStatus(); + // The MediaPlayerPrivateGStreamer superclass now processes what it needs by calling updateStates() in handleMessage() for // GST_MESSAGE_STATE_CHANGED. However, subclasses still need to override asyncStateChangeDone() to do their own stuff. didPreroll(); @@ -2404,6 +2406,17 @@ void MediaPlayerPrivateGStreamer::updateBufferingStatus(GstBufferingMode mode, d boolForPrinting(m_wasBuffering), boolForPrinting(m_isBuffering), m_previousBufferingPercentage, m_bufferingPercentage); } +void MediaPlayerPrivateGStreamer::updateBufferingStatus() +{ + std::optional percentage = queryBufferingPercentage(); + + if (!percentage.has_value()) { + GST_DEBUG_OBJECT(pipeline(), "[Buffering] Unable to determine buffering status"); + return; + } + updateBufferingStatus(GST_BUFFERING_DOWNLOAD, percentage.value(), false, false); +} + #if USE(GSTREAMER_MPEGTS) void MediaPlayerPrivateGStreamer::processMpegTsSection(GstMpegtsSection* section) { diff --git a/Source/WebCore/platform/graphics/gstreamer/MediaPlayerPrivateGStreamer.h b/Source/WebCore/platform/graphics/gstreamer/MediaPlayerPrivateGStreamer.h index 2f701ca943d3..896a2670f5b8 100644 --- a/Source/WebCore/platform/graphics/gstreamer/MediaPlayerPrivateGStreamer.h +++ b/Source/WebCore/platform/graphics/gstreamer/MediaPlayerPrivateGStreamer.h @@ -558,6 +558,7 @@ class MediaPlayerPrivateGStreamer virtual void updateDownloadBufferingFlag(); void processBufferingStats(GstMessage*); void updateBufferingStatus(GstBufferingMode, double percentage, bool resetHistory = false, bool shouldUpdateStates = true); + void updateBufferingStatus(); void updateMaxTimeLoaded(double percentage); #if USE(GSTREAMER_MPEGTS)