diff --git a/Source/WebCore/html/HTMLMediaElement.cpp b/Source/WebCore/html/HTMLMediaElement.cpp
index 7a0e66aa6e9b4..b45fa6d134784 100644
--- a/Source/WebCore/html/HTMLMediaElement.cpp
+++ b/Source/WebCore/html/HTMLMediaElement.cpp
@@ -4164,7 +4164,7 @@ void HTMLMediaElement::scanTimerFired()
// The spec says to fire periodic timeupdate events (those sent while playing) every
// "15 to 250ms", we choose the slowest frequency
-static const Seconds maxTimeupdateEventFrequency { 250_ms };
+static const Seconds maxTimeupdateEventFrequency { 200_ms };
void HTMLMediaElement::startPlaybackProgressTimer()
{
@@ -4211,8 +4211,8 @@ void HTMLMediaElement::scheduleTimeupdateEvent(bool periodicEvent)
MonotonicTime now = MonotonicTime::now();
Seconds timedelta = now - m_clockTimeAtLastUpdateEvent;
- // throttle the periodic events
- if (periodicEvent && timedelta < maxTimeupdateEventFrequency)
+ // Throttle the periodic events, but leave some room for timers that run slightly faster than expected.
+ if (periodicEvent && timedelta < (maxTimeupdateEventFrequency - 50_ms))
return;
// Some media engines make multiple "time changed" callbacks at the same time, but we only want one
diff --git a/Source/WebCore/platform/graphics/gstreamer/MediaPlayerPrivateGStreamer.cpp b/Source/WebCore/platform/graphics/gstreamer/MediaPlayerPrivateGStreamer.cpp
index 1bb518f0a228d..b478f93e6429e 100644
--- a/Source/WebCore/platform/graphics/gstreamer/MediaPlayerPrivateGStreamer.cpp
+++ b/Source/WebCore/platform/graphics/gstreamer/MediaPlayerPrivateGStreamer.cpp
@@ -565,6 +565,20 @@ void MediaPlayerPrivateGStreamer::updatePlaybackRate()
GST_INFO_OBJECT(pipeline(), mute ? "Need to mute audio" : "Do not need to mute audio");
if (m_lastPlaybackRate != m_playbackRate) {
+#if ENABLE(INSTANT_RATE_CHANGE)
+ bool didInstantRateChange = false;
+ if (!paused()) {
+ GstStructure* s = gst_structure_new("custom-instant-rate-change",
+ "rate", G_TYPE_DOUBLE, m_playbackRate, nullptr);
+ didInstantRateChange = gst_element_send_event(
+ pipeline(), gst_event_new_custom(GST_EVENT_CUSTOM_DOWNSTREAM_OOB, s));
+ }
+ if (didInstantRateChange) {
+ g_object_set(m_pipeline.get(), "mute", mute, nullptr);
+ m_lastPlaybackRate = m_playbackRate;
+ }
+ else
+#endif
if (doSeek(playbackPosition(), m_playbackRate, static_cast(GST_SEEK_FLAG_FLUSH))) {
g_object_set(m_pipeline.get(), "mute", mute, nullptr);
m_lastPlaybackRate = m_playbackRate;
diff --git a/Source/WebCore/platform/graphics/gstreamer/mse/WebKitMediaSourceGStreamer.cpp b/Source/WebCore/platform/graphics/gstreamer/mse/WebKitMediaSourceGStreamer.cpp
index 4170799b415c7..23c37e1503d38 100644
--- a/Source/WebCore/platform/graphics/gstreamer/mse/WebKitMediaSourceGStreamer.cpp
+++ b/Source/WebCore/platform/graphics/gstreamer/mse/WebKitMediaSourceGStreamer.cpp
@@ -820,7 +820,8 @@ static void webKitMediaSrcStreamFlush(Stream* stream, bool isSeekingFlush, GstCl
GST_DEBUG_OBJECT(stream->source, "Resetting segment to current pipeline running time (%" GST_TIME_FORMAT " and stream time (%" GST_TIME_FORMAT " = %s)",
GST_TIME_ARGS(pipelineRunningTime), GST_TIME_ARGS(pipelineStreamTime), streamTime.toString().ascii().data());
streamingMembers->segment.base = pipelineRunningTime;
- streamingMembers->segment.start = streamingMembers->segment.time = static_cast(pipelineStreamTime);
+ streamingMembers->segment.rate = stream->source->priv->rate;
+ streamingMembers->segment.position = streamingMembers->segment.start = streamingMembers->segment.time = static_cast(pipelineStreamTime);
}
}
}
@@ -952,6 +953,19 @@ static gboolean webKitMediaSrcSendEvent(GstElement* element, GstEvent* event)
webKitMediaSrcSeek(WEBKIT_MEDIA_SRC(element), start, rate);
return true;
}
+ case GST_EVENT_CUSTOM_DOWNSTREAM_OOB: {
+ WebKitMediaSrc* source = WEBKIT_MEDIA_SRC(element);
+ gboolean result = !source->priv->streams.isEmpty();
+ for (const RefPtr& stream : source->priv->streams.values())
+ result &= gst_pad_push_event(stream->pad.get(), gst_event_ref(event));
+ if (gst_event_has_name(event, "custom-instant-rate-change")) {
+ gdouble rate = 1.0;
+ if (gst_structure_get_double(gst_event_get_structure(event), "rate", &rate))
+ source->priv->rate = rate;
+ }
+ gst_event_unref(event);
+ return result;
+ }
default:
return GST_ELEMENT_CLASS(webkit_media_src_parent_class)->send_event(element, event);
}
diff --git a/Source/cmake/OptionsWPE.cmake b/Source/cmake/OptionsWPE.cmake
index a81bfdadd05bd..aa00de257a2a8 100644
--- a/Source/cmake/OptionsWPE.cmake
+++ b/Source/cmake/OptionsWPE.cmake
@@ -107,6 +107,7 @@ WEBKIT_OPTION_DEFINE(USE_GSTREAMER_HOLEPUNCH "Whether to enable GStreamer holepu
WEBKIT_OPTION_DEFINE(USE_EXTERNAL_HOLEPUNCH "Whether to enable external holepunch" PRIVATE OFF)
WEBKIT_OPTION_DEFINE(ENABLE_ACCELERATED_2D_CANVAS "Whether to enable accelerated 2D canvas" PRIVATE OFF)
WEBKIT_OPTION_DEFINE(ENABLE_OIPF_VK "Whether to enable OIPF keys for DAE applications" PRIVATE OFF)
+WEBKIT_OPTION_DEFINE(ENABLE_INSTANT_RATE_CHANGE "Whether to enable instant rate change" PRIVATE OFF)
# Supported platforms.
WEBKIT_OPTION_DEFINE(USE_WPEWEBKIT_PLATFORM_WESTEROS "Whether to enable support for the Westeros platform" PUBLIC OFF)