Skip to content

Commit

Permalink
[GStreamer] Buffering hysteresis and buffering improvements for Broadcom
Browse files Browse the repository at this point in the history
https://bugs.webkit.org/show_bug.cgi?id=275683

Reviewed by NOBODY (OOPS!).

The usage of a fixed buffering level to evaluate the need for buffering
results in a too unstable behaviour, with quick changes between states
that may create stuttering.

This commit adds hysteresis to the buffering level, with low and high
watermark levels that trigger "buffering needed" (when below the low
watermark), "buffering completed" (when above high watermark) and "same
state as before" when the level is between both marks.

In addition to that, the pipeline is automatically paused on a seek when
working on stream mode (using GstQueue2 for partial in-memory buffering
instead of using GstDownload for full on-disk download of the media).
This is because, when seeking, rebuffering must occur from scratch to
get the data for the new target position, and buffering level starts
from 0% in that case, which means that buffering is needed and the
pipeline must be paused. Both the m_Buffering flag and
m_bufferingPercentage are immediately forced to true and 0%, respectively.
The previous values (see explanation below) are also set to the same
values by using the new resetHistory parameter in updateBufferingStatus().
This makes sure that the "end of buffering" condition can be properly
detected.

The buffering status and buffering level computation has been
centralized in updateBufferingStatus(). Both the status and the level
now store the last value, so it's possible to check if there's been any
difference on them and to decide here on the hysteresis status
evolution.

updateStates() is where the consequences of buffering changes happen.
This method now relies in the status and level updates explained in the
previous paragraph. In the specific case of an async change, the current
values for m_isBuffering and m_bufferingPercentage are ignored (previous
ones are used). This is to be able to detect significant changes in the
hysteresis (watermark level crossing) after the async change has
completed. Otherwise, we would be detecting those crossings in a moment
where nothing can be done (pipeline state changes are ignored can can't
be enforced during async state changes).

Some extra improvements have been added to adapt to the special
circumstances of Broadcom devices, where the PlayPump Nexus component
can store a lot of data which is hidden to the vanilla buffering
algorithm. Now that data is had into account to compute the realistic
buffering level. Also, more agile low/high watermarks of 20% and 80% are
used for buffering in the case of playbin2 in stream buffering mode on
Broadcom.

20-80% levels for low/high watermark are now used on all devices
(Broadcom and not Broadcom), since usage tests proved that this makes
the buffering more agile also on Raspberry Pi, for instance.

All these changes are compatible with the Quirks framework. A new
needsBufferingPercentageCorrection() method has been added to enable the
extra buffering logic (which corrects the queue2 buffering level with the
buffer levels of PlayPump and multiqueue) on the platforms that use Nexus.
All the code to implement that logic has been hidden in the
GStreamerQuirkBroadcomBase class, from which both GStreamerQuirkBcmNexus
and GStreamerQuirkBroadcom inherit.

That code needs some elements that in principle would be very coupled with
MediaPlayerPrivateGStreamer, such as vidfilter, queue2, multiqueue and a
MovingAverage to smooth buffering levels. To avoid that coupling, they
are stored in an opaque GStreamerQuirkState, stored by the player private
but implemented by a GStreamerQuirkBroadcomBaseState subclass that only
GStreamerQuirkBroadcomBase knows how to interpret (by casting after
ensuring that the casting is legal).

There's also the problem that multiple competing quirks can be installing
at the same time, but all this buffering percentage correction code must
only be run by one (only one!) of them. To solve that, the quirks state
is configured with an owner quirk, and only that owner can use it. The
rest of the competing quirks quich are not the owner must bail out and
return default results on their buffering percentage correction method
calls.

See: WebPlatformForEmbedded/WPEWebKit#1309

* Source/WebCore/platform/SourcesGStreamer.txt: Added GStreamerQuirkBroadcomBase.
* Source/WebCore/platform/graphics/gstreamer/MediaPlayerPrivateGStreamer.cpp:
(WebCore::MediaPlayerPrivateGStreamer::play): Make code compatible with InitiallyPaused.
(WebCore::MediaPlayerPrivateGStreamer::doSeek): Pause pipeline and reset buffering level to 0% in case we're operating in stream mode.
(WebCore::MediaPlayerPrivateGStreamer::queryBufferingPercentage): Get the buffering percentage from the most relevant element and delegate to the quirks.
(WebCore::MediaPlayerPrivateGStreamer::fillTimerFired): Clarify that this method is only used in on-disk buffering. Rely on queryBufferingPercentage() now, instead of manually getting the value from here.
(WebCore::MediaPlayerPrivateGStreamer::handleMessage): Delegate to the quirks to store/delete references to specific elements in the quirks state.
(WebCore::MediaPlayerPrivateGStreamer::processBufferingStats): Delegate to the quierks to apply buffering percentage corrections on platforms that need them.
(WebCore::MediaPlayerPrivateGStreamer::updateBufferingStatus): Update the values of m_wasBuffering, m_isBuffering, m_previousBufferingPercentage, m_bufferingPercentage and m_didDownloadFinish. The first 4 attributes should *ONLY* be modified from this method, in order to guarantee coherency. The only *EXCEPTION* to this rule is the delay which can happen in updateStates(). The new resetHistory parameter sets the value both in the previous value family of attributes and in the current value family of variables.
(WebCore::MediaPlayerPrivateGStreamer::updateStates): Assume that m_wasBuffering, etc. have been precomputed by updateBufferingStatus() before updateStates() is called. In the case of an async state change, delay m_isBuffering and m_bufferingPercentage "to the past" (m_wasBuffering and m_previousBufferingPercentage), so that any change on them can be detected on the next iteration.
* Source/WebCore/platform/graphics/gstreamer/MediaPlayerPrivateGStreamer.h: Added new m_wasBuffering and m_previousBufferingPercentage attributes to store previous values of m_isBuffering and m_bufferingPercentage and ease detection of changes. Added quirks state.
(WebCore::MediaPlayerPrivateGStreamer::shouldDownload): Needed by quirks.
(WebCore::MediaPlayerPrivateGStreamer::setQuirkState): Configure a quirks state on the player private.
(WebCore::MediaPlayerPrivateGStreamer::quirkState): Get a quirks state from the player private.
* Source/WebCore/platform/gstreamer/GStreamerHolePunchQuirkBcmNexus.h: identifier() is now const.
* Source/WebCore/platform/gstreamer/GStreamerHolePunchQuirkFake.h: Ditto.
* Source/WebCore/platform/gstreamer/GStreamerHolePunchQuirkRialto.h: Ditto.
* Source/WebCore/platform/gstreamer/GStreamerHolePunchQuirkWesteros.h: Ditto.
* Source/WebCore/platform/gstreamer/GStreamerQuirkAmLogic.h: Ditto.
* Source/WebCore/platform/gstreamer/GStreamerQuirkBcmNexus.h: Ditto. Inherit from base class.
* Source/WebCore/platform/gstreamer/GStreamerQuirkBroadcom.h: Ditto. Inherit from base class.
* Source/WebCore/platform/gstreamer/GStreamerQuirkBroadcomBase.cpp: Added. Base class for GStreamerQuirkBcmNexus and GStreamerQuirkBroadcom.
(WebCore::GStreamerQuirkBroadcomBase::GStreamerQuirkBroadcomBase): Constructor.
(WebCore::GStreamerQuirkBroadcomBase::queryBufferingPercentage const): Get the buffering percentage from Queue2 if present and the player shouldn't download (streaming mode).
(WebCore::GStreamerQuirkBroadcomBase::correctBufferingPercentage const): On systems using PlayPump (eg: Broadcom Nexus), correct the buffering percentage with the data stored in multiqueue and PlayPump in order to make the buffering percentage more realistic. Uses the MovingAverage.
(WebCore::GStreamerQuirkBroadcomBase::resetBufferingPercentage const): Sets the percentage to a given value in the MovingAverage.
(WebCore::GStreamerQuirkBroadcomBase::setupBufferingPercentageCorrection const): Gets/deletes references to vidfilter, multiqueue and queue2 in the quirks state.
(WebCore::GStreamerQuirkBroadcomBase::isEnsuredOwnedState const): Creates the quirk state if not present (if it's still assigned to the GStreamerQuirkState superclass), assigning it to an instance of the GStreamerQuirkBroadcomBaseState subclass and setting the current GStreamerQuirkBroadcomBase implementation (GStreamerQuirkBcmNexus or GStreamerQuirkBroadcom, first come first served) as owner. If the state had been initialized with a subclass before, returns false, so the caller can bail out.
* Source/WebCore/platform/gstreamer/GStreamerQuirkBroadcomBase.h: Added.
(WebCore::GStreamerQuirkBroadcomBase::needsBufferingPercentageCorrection const): True for this class and derived ones.
(WebCore::GStreamerQuirkBroadcomBase::MovingAverage::MovingAverage): Initialize an array of "length" number of elements.
(WebCore::GStreamerQuirkBroadcomBase::MovingAverage::reset): Reset all the values to a given value (usually zero).
(WebCore::GStreamerQuirkBroadcomBase::MovingAverage::accumulate): Insert a new value in the array and recompute the moving average.
(WebCore::GStreamerQuirkBroadcomBase::GStreamerQuirkBroadcomBaseState::GStreamerQuirkBroadcomBaseState): Concrete GStreamerQuirkState that stores all the elements and the MovingAverage associated to a specific MediaPlayerPrivateGStreamer that GStreamerQuirkBroadcomBase needs to do its job. This avoids getting MediaPlayerPrivateGStreamer dirty with extra attributes that are only relevant for platforms with quirks.
* Source/WebCore/platform/gstreamer/GStreamerQuirkRealtek.h: identifier() is now const.
* Source/WebCore/platform/gstreamer/GStreamerQuirkRialto.h: Ditto.
* Source/WebCore/platform/gstreamer/GStreamerQuirkWesteros.h: Ditto.
* Source/WebCore/platform/gstreamer/GStreamerQuirks.cpp: Added new methods.
(WebCore::GStreamerQuirksManager::needsBufferingPercentageCorrection const): True if any configured quirk needs it.
(WebCore::GStreamerQuirksManager::queryBufferingPercentage const): Forward query request to any configured quirk that can fulfill it.
(WebCore::GStreamerQuirksManager::correctBufferingPercentage const): Forward correction request to any configured quirk that can fulfill it.
(WebCore::GStreamerQuirksManager::resetBufferingPercentage const): Ditto, but for reset.
(WebCore::GStreamerQuirksManager::setupBufferingPercentageCorrection const): Ditto, but for setup. The first quirk subclass that sets a quirk state in this call or any of the others will be the *only* subclass that will operate the buffer percentage correnction, and the rest will bail out. The first one coming is the only one that will work from that moment on. The rest will be incompatible with that specific configured quirk state and will bail out.
* Source/WebCore/platform/gstreamer/GStreamerQuirks.h:
(WebCore::GStreamerQuirkBase::GStreamerQuirkState:::m_owner): Pointer to identify the specific GStreamerQuirk instance that owns the state. This pointer is *not* used to access the quirk.
(WebCore::GStreamerQuirkBase::GStreamerQuirkState::GStreamerQuirkState): Constructor. No owner needed (on purpose, to mark the "unconfigured" state.
(WebCore::GStreamerQuirkBase::GStreamerQuirkState::isOwnedBy): True if owned by the specific GStreamerQuirk passed as parameter.
(WebCore::GStreamerQuirkBase::GStreamerQuirkState::isOwned): True if owned by any GStreamerQuirk. False if still unconfigured/unowned.
(WebCore::GStreamerQuirk::needsBufferingPercentageCorrection const): Default empty implementation.
(WebCore::GStreamerQuirk::queryBufferingPercentage const): Ditto.
(WebCore::GStreamerQuirk::correctBufferingPercentage const): Ditto.
(WebCore::GStreamerQuirk::resetBufferingPercentage const): Ditto.
(WebCore::GStreamerQuirk::setupBufferingPercentageCorrection const): Ditto.
  • Loading branch information
eocanha committed Jul 10, 2024
1 parent f073c57 commit 0aae941
Show file tree
Hide file tree
Showing 17 changed files with 546 additions and 71 deletions.
1 change: 1 addition & 0 deletions Source/WebCore/platform/SourcesGStreamer.txt
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,7 @@ platform/gstreamer/GStreamerHolePunchQuirkWesteros.cpp
platform/gstreamer/GStreamerQuirkAmLogic.cpp
platform/gstreamer/GStreamerQuirkBcmNexus.cpp
platform/gstreamer/GStreamerQuirkBroadcom.cpp
platform/gstreamer/GStreamerQuirkBroadcomBase.cpp
platform/gstreamer/GStreamerQuirkRealtek.cpp
platform/gstreamer/GStreamerQuirkRialto.cpp
platform/gstreamer/GStreamerQuirkWesteros.cpp
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -454,7 +454,8 @@ void MediaPlayerPrivateGStreamer::play()
}

if (!m_playbackRate) {
if (m_playbackRatePausedState == PlaybackRatePausedState::ManuallyPaused)
if (m_playbackRatePausedState == PlaybackRatePausedState::InitiallyPaused
|| m_playbackRatePausedState == PlaybackRatePausedState::ManuallyPaused)
m_playbackRatePausedState = PlaybackRatePausedState::RatePaused;
return;
}
Expand Down Expand Up @@ -573,6 +574,20 @@ bool MediaPlayerPrivateGStreamer::doSeek(const SeekTarget& target, float rate)
return true;
}

// Stream mode. Seek will automatically deplete buffer level, so we always want to pause the pipeline and wait until the
// buffer is replenished. But we don't want this behaviour on immediate seeks that only change the playback rate.
if (!m_downloadBuffer && !m_isChangingRate) {
GST_DEBUG_OBJECT(pipeline(), "[Buffering] Pausing pipeline, resetting buffering level to 0 and forcing m_isBuffering true before seeking on stream mode");

auto& quirksManager = GStreamerQuirksManager::singleton();
if (quirksManager.isEnabled())
quirksManager.resetBufferingPercentage(this, 0);

// Make sure that m_isBuffering is set to true, so that when buffering completes it's set to false again and playback resumes.
updateBufferingStatus(GST_BUFFERING_STREAM, 0.0, true);
changePipelineState(GST_STATE_PAUSED);
}

auto seekStart = toGstClockTime(startTime);
auto seekStop = toGstClockTime(endTime);
GST_DEBUG_OBJECT(pipeline(), "[Seek] Performing actual seek to %" GST_TIMEP_FORMAT " (endTime: %" GST_TIMEP_FORMAT ") at rate %f", &seekStart, &seekStop, rate);
Expand Down Expand Up @@ -1313,6 +1328,50 @@ void MediaPlayerPrivateGStreamer::commitLoad()
updateStates();
}

bool MediaPlayerPrivateGStreamer::queryBufferingPercentage(GstBufferingMode& mode, int &percentage)
{
GRefPtr<GstQuery> query = adoptGRef(gst_query_new_buffering(GST_FORMAT_PERCENT));

bool isQueryOk = false;
const char* elementName = "<undefined>";

auto& quirksManager = GStreamerQuirksManager::singleton();
if (!isQueryOk)
isQueryOk = quirksManager.isEnabled() && quirksManager.queryBufferingPercentage(this, elementName, query);

if (!isQueryOk) {
isQueryOk = (m_audioSink && gst_element_query(m_audioSink.get(), query.get()));
if (isQueryOk)
elementName = "audiosink";
}
if (!isQueryOk) {
isQueryOk = (m_videoSink && gst_element_query(m_videoSink.get(), query.get()));
if (isQueryOk)
elementName = "videosink";
}
if (!isQueryOk) {
isQueryOk = gst_element_query(m_pipeline.get(), query.get());
if (isQueryOk)
elementName = "pipeline";
}
if (!isQueryOk)
return false;

percentage = 0;
gst_query_parse_buffering_percent(query.get(), nullptr, &percentage);
gst_query_parse_buffering_stats(query.get(), &mode, nullptr, nullptr, nullptr);

GST_TRACE_OBJECT(pipeline(), "[Buffering] %s reports %d buffering", elementName, percentage);

ASSERT(mode == GST_BUFFERING_DOWNLOAD);
if (mode != GST_BUFFERING_DOWNLOAD)
GST_WARNING_OBJECT(pipeline(), "[Buffering] mode isn't GST_BUFFERING_DOWNLOAD, but it should be!");

// No correction is done to the percentage on Broadcom because that's only done for the GST_BUFFERING_STREAM case, which can't happen here.
return true;
}

// This method is only called when doing on-disk buffering. No need to apply any of the extra corrections done for Broadcom when stream buffering.
void MediaPlayerPrivateGStreamer::fillTimerFired()
{
if (m_didErrorOccur) {
Expand All @@ -1321,15 +1380,12 @@ void MediaPlayerPrivateGStreamer::fillTimerFired()
return;
}

GRefPtr<GstQuery> query = adoptGRef(gst_query_new_buffering(GST_FORMAT_PERCENT));
double fillStatus = 100.0;
GstBufferingMode mode = GST_BUFFERING_DOWNLOAD;
int percentage = m_bufferingPercentage;
bool isQuerySuccessful = queryBufferingPercentage(mode, percentage);

if (gst_element_query(pipeline(), query.get())) {
gst_query_parse_buffering_stats(query.get(), &mode, nullptr, nullptr, nullptr);

int percentage;
gst_query_parse_buffering_percent(query.get(), nullptr, &percentage);
if (isQuerySuccessful) {
fillStatus = percentage;
} else if (m_httpResponseTotalSize) {
GST_DEBUG_OBJECT(pipeline(), "[Buffering] Query failed, falling back to network read position estimation");
Expand Down Expand Up @@ -2023,6 +2079,9 @@ void MediaPlayerPrivateGStreamer::handleMessage(GstMessage* message)
}
}

if (quirksManager.isEnabled() && quirksManager.needsBufferingPercentageCorrection())
quirksManager.setupBufferingPercentageCorrection(this, currentState, newState, GST_ELEMENT(GST_MESSAGE_SRC(message)));

if (!messageSourceIsPlaybin || m_isDelayingLoad)
break;

Expand Down Expand Up @@ -2199,7 +2258,11 @@ void MediaPlayerPrivateGStreamer::processBufferingStats(GstMessage* message)
int percentage;
gst_message_parse_buffering(message, &percentage);

updateBufferingStatus(mode, percentage);
auto& quirksManager = GStreamerQuirksManager::singleton();
if (quirksManager.isEnabled() && quirksManager.needsBufferingPercentageCorrection())
percentage = quirksManager.correctBufferingPercentage(this, percentage, mode);

updateBufferingStatus(mode, static_cast<double>(percentage));
}

void MediaPlayerPrivateGStreamer::updateMaxTimeLoaded(double percentage)
Expand All @@ -2212,44 +2275,71 @@ void MediaPlayerPrivateGStreamer::updateMaxTimeLoaded(double percentage)
GST_DEBUG_OBJECT(pipeline(), "[Buffering] Updated maxTimeLoaded: %s", toString(m_maxTimeLoaded).utf8().data());
}

void MediaPlayerPrivateGStreamer::updateBufferingStatus(GstBufferingMode mode, double percentage)
void MediaPlayerPrivateGStreamer::updateBufferingStatus(GstBufferingMode mode, double percentage, bool resetHistory)
{
bool wasBuffering = m_isBuffering;
// m_wasBuffering, m_isBuffering, m_previousBufferingPercentage and m_bufferingPercentage can ONLY be modified from this method.
m_wasBuffering = m_isBuffering;
m_previousBufferingPercentage = m_bufferingPercentage;

#ifndef GST_DISABLE_GST_DEBUG
GUniquePtr<char> modeString(g_enum_to_string(GST_TYPE_BUFFERING_MODE, mode));
GST_DEBUG_OBJECT(pipeline(), "[Buffering] mode: %s, status: %f%%", modeString.get(), percentage);
#endif

m_didDownloadFinish = percentage == 100;
double highWatermark = 100.0;
double lowWatermark = 100.0;
if (mode == GST_BUFFERING_STREAM && m_isLegacyPlaybin) {
highWatermark = 80.0;
lowWatermark = 20.0;
}

if (!m_didDownloadFinish)
// Hysteresis for m_didDownloadFinish.
if (m_didDownloadFinish && percentage < lowWatermark) {
GST_TRACE("[Buffering] m_didDownloadFinish: %s, percentage: %f, lowWatermark: %f. Setting m_didDownloadFinish to false",
boolForPrinting(m_didDownloadFinish), percentage, lowWatermark);
m_didDownloadFinish = false;
} else if (!m_didDownloadFinish && percentage >= highWatermark) {
GST_TRACE("[Buffering] m_didDownloadFinish: %s, percentage: %f, highWatermark: %f. Setting m_didDownloadFinish to true",
boolForPrinting(m_didDownloadFinish), percentage, highWatermark);
m_didDownloadFinish = true;
} else {
GST_TRACE("[Buffering] m_didDownloadFinish remains %s, lowWatermark: %f, percentage: %f, highWatermark: %f",
boolForPrinting(m_didDownloadFinish), lowWatermark, percentage, highWatermark);
}

// Hysteresis for m_isBuffering.
if (!m_isBuffering && percentage < lowWatermark) {
GST_TRACE("[Buffering] m_isBuffering: %s, percentage: %f, lowWatermark: %f. Setting m_isBuffering to true",
boolForPrinting(m_isBuffering), percentage, lowWatermark);
m_isBuffering = true;
else
} else if (m_isBuffering && percentage >= highWatermark) {
GST_TRACE("[Buffering] m_isBuffering: %s, percentage: %f, highWatermark: %f. Setting m_isBuffering to false",
boolForPrinting(m_isBuffering), percentage, highWatermark);
m_isBuffering = false;
} else {
GST_TRACE("[Buffering] m_isBuffering remains %s, lowWatermark: %f, percentage: %f, highWatermark: %f",
boolForPrinting(m_isBuffering), lowWatermark, percentage, highWatermark);
}

if (m_didDownloadFinish)
m_fillTimer.stop();

m_bufferingPercentage = percentage;
switch (mode) {
case GST_BUFFERING_STREAM: {
updateMaxTimeLoaded(percentage);

m_bufferingPercentage = percentage;
if (m_didDownloadFinish || !wasBuffering)
updateStates();

break;
}
case GST_BUFFERING_DOWNLOAD: {
updateMaxTimeLoaded(percentage);
updateStates();
break;
}
default:
#ifndef GST_DISABLE_GST_DEBUG
GST_DEBUG_OBJECT(pipeline(), "Unhandled buffering mode: %s", modeString.get());
#endif
break;
}
// resetHistory is used to forget about the past values and set them like the new ones. This is useful when resetting
// the percentage to 0 before a seek, in order to prevent that setting to be undone by chance in updateStates() if
// the pipeline is in GST_STATE_CHANGE_ASYNC. We want to make sure that we start from an m_isBuffering true state, so
// that the change to m_isBuffering false is detected. We want to prevent updateStates() undoing a change to true and
// keeping m_isBuffering to false, delay it, and when the buffering percentage reaches the high watermark it's ignored
// because of m_isBuffering being false because of the delay.
if (resetHistory) {
m_wasBuffering = m_isBuffering;
m_previousBufferingPercentage = m_bufferingPercentage;
}
updateMaxTimeLoaded(percentage);
updateStates();
GST_TRACE("[Buffering] Settled results: m_wasBuffering: %s, m_isBuffering: %s, m_previousBufferingPercentage: %d, m_bufferingPercentage: %d",
boolForPrinting(m_wasBuffering), boolForPrinting(m_isBuffering), m_previousBufferingPercentage, m_bufferingPercentage);
}

#if USE(GSTREAMER_MPEGTS)
Expand Down Expand Up @@ -2534,6 +2624,9 @@ void MediaPlayerPrivateGStreamer::updateStates()
stateReallyChanged = true;
}

// updateBufferingStatus() must have been called at some point before updateStates() and have set m_wasBuffering, m_isBuffering,
// m_previousBufferingPercentage and m_bufferingPercentage. We take decisions here based on their values.

bool shouldUpdatePlaybackState = false;
switch (getStateResult) {
case GST_STATE_CHANGE_SUCCESS: {
Expand All @@ -2546,8 +2639,6 @@ void MediaPlayerPrivateGStreamer::updateStates()

m_shouldResetPipeline = m_currentState <= GST_STATE_READY;

bool didBuffering = m_isBuffering;

// Update ready and network states.
switch (m_currentState) {
case GST_STATE_NULL:
Expand All @@ -2562,18 +2653,8 @@ void MediaPlayerPrivateGStreamer::updateStates()
FALLTHROUGH;
case GST_STATE_PLAYING: {
bool isLooping = player && player->isLooping();
if (m_isBuffering) {
GRefPtr<GstQuery> query = adoptGRef(gst_query_new_buffering(GST_FORMAT_PERCENT));

m_isBuffering = m_bufferingPercentage < 100;
if ((m_audioSink && gst_element_query(m_audioSink.get(), query.get()))
|| (m_videoSink && gst_element_query(m_videoSink.get(), query.get()))
|| gst_element_query(m_pipeline.get(), query.get())) {
gboolean isBuffering = m_isBuffering;
gst_query_parse_buffering_percent(query.get(), &isBuffering, nullptr);
GST_TRACE_OBJECT(pipeline(), "[Buffering] m_isBuffering forcefully updated from %d to %d", m_isBuffering, isBuffering);
m_isBuffering = isBuffering;
}
if (m_wasBuffering) {
GST_TRACE("[Buffering] m_isBuffering: %s --> %s", boolForPrinting(m_wasBuffering), boolForPrinting(m_isBuffering));

if (!m_isBuffering) {
GST_INFO_OBJECT(pipeline(), "[Buffering] Complete.");
Expand Down Expand Up @@ -2607,7 +2688,7 @@ void MediaPlayerPrivateGStreamer::updateStates()
m_areVolumeAndMuteInitialized = true;
}

if ((didBuffering && !m_isBuffering && !m_isPaused && m_playbackRate)
if ((m_wasBuffering && !m_isBuffering && !m_isPaused && m_playbackRatePausedState != PlaybackRatePausedState::ManuallyPaused && m_playbackRate)
|| m_playbackRatePausedState == PlaybackRatePausedState::ShouldMoveToPlaying) {
m_playbackRatePausedState = PlaybackRatePausedState::Playing;
GST_INFO_OBJECT(pipeline(), "[Buffering] Restarting playback (because of buffering or resuming from zero playback rate)");
Expand All @@ -2616,7 +2697,7 @@ void MediaPlayerPrivateGStreamer::updateStates()
} else if (m_currentState == GST_STATE_PLAYING) {
m_isPaused = false;

shouldPauseForBuffering = (m_isBuffering && !m_isLiveStream.value_or(false));
shouldPauseForBuffering = (!m_wasBuffering && m_isBuffering && !m_isLiveStream.value_or(false));
if (shouldPauseForBuffering || !m_playbackRate) {
GST_INFO_OBJECT(pipeline(), "[Buffering] Pausing stream for buffering or because of zero playback rate.");
changePipelineState(GST_STATE_PAUSED);
Expand Down Expand Up @@ -2645,6 +2726,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));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -249,6 +249,9 @@ class MediaPlayerPrivateGStreamer
AbortableTaskQueue& sinkTaskQueue() { return m_sinkTaskQueue; }

String codecForStreamId(const String& streamId);
bool shouldDownload() { return m_fillTimer.isActive(); }
void setQuirkState(GStreamerQuirkBase::GStreamerQuirkState&& state) { m_quirkState = WTFMove(state); }
GStreamerQuirkBase::GStreamerQuirkState& quirkState() { return m_quirkState; }

protected:
enum MainThreadNotification {
Expand All @@ -262,13 +265,20 @@ class MediaPlayerPrivateGStreamer
};

enum class PlaybackRatePausedState {
ManuallyPaused, // Initialization or user explicitly paused. This takes preference over RatePaused. You don't
// transition from Manually to Rate Paused unless there is a play while rate == 0.
RatePaused, // Pipeline was playing and rate was set to zero.
ShouldMoveToPlaying, // Pipeline was paused because of zero rate and it should be playing. This is not a
// definitive state, just an operational transition from RatePaused to Playing to keep the
// pipeline state changes contained in updateStates.
Playing, // Pipeline is playing and it should be.
// Initialization. This takes preference over RatePaused. You don't
// transition from Initially to Rate Paused unless there is a play while rate == 0.
InitiallyPaused,
// User explicitly paused. This takes preference over RatePaused. You don't
// transition from Manually to Rate Paused unless there is a play while rate == 0.
ManuallyPaused,
// Pipeline was playing and rate was set to zero.
RatePaused,
// Pipeline was paused because of zero rate and it should be playing. This is not a
// definitive state, just an operational transition from RatePaused to Playing to keep the
// pipeline state changes contained in updateStates.
ShouldMoveToPlaying,
// Pipeline is playing and it should be.
Playing,
};

enum class ChangePipelineStateResult {
Expand Down Expand Up @@ -378,7 +388,7 @@ class MediaPlayerPrivateGStreamer
// https://bugs.webkit.org/show_bug.cgi?id=260385
bool m_isPaused { true };
float m_playbackRate { 1 };
PlaybackRatePausedState m_playbackRatePausedState { PlaybackRatePausedState::ManuallyPaused };
PlaybackRatePausedState m_playbackRatePausedState { PlaybackRatePausedState::InitiallyPaused };
GstState m_currentState { GST_STATE_NULL };
GstState m_oldState { GST_STATE_NULL };
GstState m_requestedState { GST_STATE_VOID_PENDING };
Expand Down Expand Up @@ -498,7 +508,7 @@ class MediaPlayerPrivateGStreamer

virtual void updateDownloadBufferingFlag();
void processBufferingStats(GstMessage*);
void updateBufferingStatus(GstBufferingMode, double percentage);
void updateBufferingStatus(GstBufferingMode, double percentage, bool resetHistory = false);
void updateMaxTimeLoaded(double percentage);

#if USE(GSTREAMER_MPEGTS)
Expand All @@ -522,6 +532,8 @@ class MediaPlayerPrivateGStreamer

void configureElementPlatformQuirks(GstElement*);

bool queryBufferingPercentage(GstBufferingMode&, int &percentage);

void setPlaybinURL(const URL& urlString);

void updateTracks(const GRefPtr<GstObject>& collectionOwner);
Expand Down Expand Up @@ -566,8 +578,12 @@ class MediaPlayerPrivateGStreamer
RefPtr<TextureMapperPlatformLayerProxy> m_platformLayerProxy;
#endif
#endif
// Can only be changed from updateBufferingStatus().
bool m_wasBuffering { false };
bool m_isBuffering { false };
int m_previousBufferingPercentage { 0 };
int m_bufferingPercentage { 0 };

bool m_hasWebKitWebSrcSentEOS { false };
mutable unsigned long long m_totalBytes { 0 };
URL m_url;
Expand Down Expand Up @@ -655,6 +671,7 @@ class MediaPlayerPrivateGStreamer
RefPtr<PlatformMediaResourceLoader> m_loader;

RefPtr<GStreamerQuirksManager> m_quirksManagerForTesting;
GStreamerQuirkBase::GStreamerQuirkState m_quirkState;
};

}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ namespace WebCore {

class GStreamerHolePunchQuirkBcmNexus final : public GStreamerHolePunchQuirk {
public:
const char* identifier() final { return "BcmNexusHolePunch"; }
const char* identifier() const final { return "BcmNexusHolePunch"; }

// NOTE: We don't override createHolePunchVideoSink here because autovideosink takes care of
// auto-plugging the right sink.
Expand Down
Loading

0 comments on commit 0aae941

Please sign in to comment.