diff --git a/LayoutTests/platform/wpe/media/video-punch-hole-expected.txt b/LayoutTests/platform/wpe/media/video-punch-hole-expected.txt new file mode 100644 index 0000000000000..6713965d3ae78 --- /dev/null +++ b/LayoutTests/platform/wpe/media/video-punch-hole-expected.txt @@ -0,0 +1,21 @@ + +(GraphicsLayer + (anchor 0.00 0.00) + (bounds 1288.00 807.00) + (children 1 + (GraphicsLayer + (bounds 1288.00 807.00) + (contentsOpaque 1) + (children 1 + (GraphicsLayer + (position 8.00 8.00) + (bounds 1280.00 720.00) + ) + ) + ) + ) +) +RUN(internals.enableGStreamerHolePunching(video)) +RUN(video.src = findMediaFile('video', '../../../media/content/test')) +RUN(video.play()) + diff --git a/LayoutTests/platform/wpe/media/video-punch-hole.html b/LayoutTests/platform/wpe/media/video-punch-hole.html new file mode 100644 index 0000000000000..532e9f35d7974 --- /dev/null +++ b/LayoutTests/platform/wpe/media/video-punch-hole.html @@ -0,0 +1,27 @@ + + + + + + + + + +

+
+
diff --git a/Source/WebCore/html/HTMLVideoElement.h b/Source/WebCore/html/HTMLVideoElement.h
index 08cb2b00d6499..d76e1ee010f03 100644
--- a/Source/WebCore/html/HTMLVideoElement.h
+++ b/Source/WebCore/html/HTMLVideoElement.h
@@ -119,6 +119,11 @@ class HTMLVideoElement final : public HTMLMediaElement, public Supplementable&&);
     void cancelVideoFrameCallback(unsigned);
 
+#if USE(GSTREAMER)
+    void enableGStreamerHolePunching() { m_enableGStreamerHolePunching = true; }
+    bool isGStreamerHolePunchingEnabled() const final { return m_enableGStreamerHolePunching; }
+#endif
+
 private:
     HTMLVideoElement(const QualifiedName&, Document&, bool createdByParser);
 
@@ -175,6 +180,10 @@ class HTMLVideoElement final : public HTMLMediaElement, public Supplementable> m_videoFrameRequests;
     Vector> m_servicedVideoFrameRequests;
     unsigned m_nextVideoFrameRequestIndex { 0 };
+
+#if USE(GSTREAMER)
+    bool m_enableGStreamerHolePunching { false };
+#endif
 };
 
 } // namespace WebCore
diff --git a/Source/WebCore/platform/GStreamer.cmake b/Source/WebCore/platform/GStreamer.cmake
index 066e594942bc6..eaea38f4bb005 100644
--- a/Source/WebCore/platform/GStreamer.cmake
+++ b/Source/WebCore/platform/GStreamer.cmake
@@ -67,6 +67,14 @@ if (ENABLE_VIDEO OR ENABLE_WEB_AUDIO)
         platform/graphics/gstreamer/mse/WebKitMediaSourceGStreamer.cpp
 
         platform/gstreamer/GStreamerCodecUtilities.cpp
+        platform/gstreamer/GStreamerHolePunchQuirkBcmNexus.cpp
+        platform/gstreamer/GStreamerHolePunchQuirkWesteros.cpp
+        platform/gstreamer/GStreamerQuirkAmLogic.cpp
+        platform/gstreamer/GStreamerQuirkBcmNexus.cpp
+        platform/gstreamer/GStreamerQuirkBroadcom.cpp
+        platform/gstreamer/GStreamerQuirkRealtek.cpp
+        platform/gstreamer/GStreamerQuirkWesteros.cpp
+        platform/gstreamer/GStreamerQuirks.cpp
         platform/gstreamer/VideoEncoderPrivateGStreamer.cpp
 
         platform/mediarecorder/MediaRecorderPrivateGStreamer.cpp
diff --git a/Source/WebCore/platform/audio/gstreamer/AudioDestinationGStreamer.cpp b/Source/WebCore/platform/audio/gstreamer/AudioDestinationGStreamer.cpp
index 947aad84e8c61..84cd5c14999e6 100644
--- a/Source/WebCore/platform/audio/gstreamer/AudioDestinationGStreamer.cpp
+++ b/Source/WebCore/platform/audio/gstreamer/AudioDestinationGStreamer.cpp
@@ -27,6 +27,7 @@
 #include "AudioSourceProvider.h"
 #include "AudioUtilities.h"
 #include "GStreamerCommon.h"
+#include "GStreamerQuirks.h"
 #include "Logging.h"
 #include "WebKitAudioSinkGStreamer.h"
 #include "WebKitWebAudioSourceGStreamer.h"
@@ -124,34 +125,19 @@ AudioDestinationGStreamer::AudioDestinationGStreamer(AudioIOCallback& callback,
     m_src = GST_ELEMENT_CAST(g_object_new(WEBKIT_TYPE_WEB_AUDIO_SRC, "rate", sampleRate,
         "bus", m_renderBus.get(), "destination", this, "frames", AudioUtilities::renderQuantumSize, nullptr));
 
-#if PLATFORM(AMLOGIC)
-    // autoaudiosink changes child element state to READY internally in auto detection phase
-    // that causes resource acquisition in some cases interrupting any playback already running.
-    // On Amlogic we need to set direct-mode=false prop before changing state to READY
-    // but this is not possible with autoaudiosink.
-    GRefPtr audioSink = makeGStreamerElement("amlhalasink", nullptr);
-    ASSERT_WITH_MESSAGE(audioSink, "amlhalasink should be available in the system but it is not");
-    g_object_set(audioSink.get(), "direct-mode", FALSE, nullptr);
-#else
-    GRefPtr audioSink = createPlatformAudioSink("music"_s);
-#endif
+    auto& quirksManager = GStreamerQuirksManager::singleton();
+    GRefPtr audioSink = quirksManager.createWebAudioSink();
     m_audioSinkAvailable = audioSink;
     if (!audioSink) {
         GST_ERROR("Failed to create GStreamer audio sink element");
         return;
     }
 
-    // Probe platform early on for a working audio output device. This is not needed for the WebKit
-    // custom audio sink because it doesn't rely on autoaudiosink.
-    if (!WEBKIT_IS_AUDIO_SINK(audioSink.get())) {
+    // Probe platform early on for a working audio output device in autoaudiosink.
+    if (g_str_has_prefix(GST_OBJECT_NAME(audioSink.get()), "autoaudiosink")) {
         g_signal_connect(audioSink.get(), "child-added", G_CALLBACK(+[](GstChildProxy*, GObject* object, gchar*, gpointer) {
             if (GST_IS_AUDIO_BASE_SINK(object))
                 g_object_set(GST_AUDIO_BASE_SINK(object), "buffer-time", static_cast(100000), nullptr);
-
-#if PLATFORM(REALTEK)
-            if (!g_strcmp0(G_OBJECT_TYPE_NAME(object), "GstRTKAudioSink"))
-                g_object_set(object, "media-tunnel", FALSE, "audio-service", TRUE, nullptr);
-#endif
         }), nullptr);
 
         // Autoaudiosink does the real sink detection in the GST_STATE_NULL->READY transition
diff --git a/Source/WebCore/platform/audio/gstreamer/AudioFileReaderGStreamer.cpp b/Source/WebCore/platform/audio/gstreamer/AudioFileReaderGStreamer.cpp
index 0fc782889662f..55ec4bccbb2da 100644
--- a/Source/WebCore/platform/audio/gstreamer/AudioFileReaderGStreamer.cpp
+++ b/Source/WebCore/platform/audio/gstreamer/AudioFileReaderGStreamer.cpp
@@ -24,6 +24,7 @@
 
 #include "AudioBus.h"
 #include "GStreamerCommon.h"
+#include "GStreamerQuirks.h"
 #include 
 #include 
 #include 
@@ -85,11 +86,11 @@ class AudioFileReader : public CanMakeWeakPtr {
     bool m_errorOccurred { false };
 };
 
-#if PLATFORM(BCM_NEXUS) || PLATFORM(BROADCOM) || PLATFORM(REALTEK)
 int decodebinAutoplugSelectCallback(GstElement*, GstPad*, GstCaps*, GstElementFactory* factory, gpointer)
 {
     static int GST_AUTOPLUG_SELECT_SKIP;
     static int GST_AUTOPLUG_SELECT_TRY;
+    static Vector pluginsToSkip;
     static std::once_flag onceFlag;
     std::call_once(onceFlag, [] {
         GEnumClass* enumClass = G_ENUM_CLASS(g_type_class_ref(g_type_from_name("GstAutoplugSelectResult")));
@@ -98,24 +99,10 @@ int decodebinAutoplugSelectCallback(GstElement*, GstPad*, GstCaps*, GstElementFa
         value = g_enum_get_value_by_name(enumClass, "GST_AUTOPLUG_SELECT_TRY");
         GST_AUTOPLUG_SELECT_TRY = value->value;
         g_type_class_unref(enumClass);
+
+        pluginsToSkip = GStreamerQuirksManager::singleton().disallowedWebAudioDecoders();
     });
 
-    const Vector pluginsToSkip = {
-#if PLATFORM(BCM_NEXUS) || PLATFORM(BROADCOM)
-        "brcmaudfilter"_s,
-#endif
-#if PLATFORM(REALTEK)
-        "omxaacdec"_s,
-        "omxac3dec"_s,
-        "omxac4dec"_s,
-        "omxeac3dec"_s,
-        "omxflacdec"_s,
-        "omxlpcmdec"_s,
-        "omxmp3dec"_s,
-        "omxopusdec"_s,
-        "omxvorbisdec"_s,
-#endif
-    };
     auto factoryName = StringView::fromLatin1(gst_plugin_feature_get_name(GST_PLUGIN_FEATURE(factory)));
     for (const auto& pluginToSkip : pluginsToSkip) {
         if (pluginToSkip == factoryName)
@@ -123,7 +110,6 @@ int decodebinAutoplugSelectCallback(GstElement*, GstPad*, GstCaps*, GstElementFa
     }
     return GST_AUTOPLUG_SELECT_TRY;
 }
-#endif
 
 static void copyGstreamerBuffersToAudioChannel(const GRefPtr& buffers, AudioChannel* audioChannel)
 {
@@ -173,9 +159,7 @@ AudioFileReader::~AudioFileReader()
 
     if (m_decodebin) {
         g_signal_handlers_disconnect_matched(m_decodebin.get(), G_SIGNAL_MATCH_DATA, 0, 0, nullptr, nullptr, this);
-#if PLATFORM(BCM_NEXUS) || PLATFORM(BROADCOM) || PLATFORM(REALTEK)
         g_signal_handlers_disconnect_matched(m_decodebin.get(), G_SIGNAL_MATCH_FUNC, 0, 0, nullptr, reinterpret_cast(decodebinAutoplugSelectCallback), nullptr);
-#endif
         m_decodebin = nullptr;
     }
 
@@ -426,9 +410,7 @@ void AudioFileReader::decodeAudioForBusCreation()
     g_object_set(source, "stream", memoryStream.get(), nullptr);
 
     m_decodebin = makeGStreamerElement("decodebin", "decodebin");
-#if PLATFORM(BCM_NEXUS) || PLATFORM(BROADCOM) || PLATFORM(REALTEK)
     g_signal_connect(m_decodebin.get(), "autoplug-select", G_CALLBACK(decodebinAutoplugSelectCallback), nullptr);
-#endif
     g_signal_connect_swapped(m_decodebin.get(), "pad-added", G_CALLBACK(decodebinPadAddedCallback), this);
 
     gst_bin_add_many(GST_BIN(m_pipeline.get()), source, m_decodebin.get(), nullptr);
diff --git a/Source/WebCore/platform/graphics/MediaPlayer.cpp b/Source/WebCore/platform/graphics/MediaPlayer.cpp
index 76832faee329e..a6445f5ce22b2 100644
--- a/Source/WebCore/platform/graphics/MediaPlayer.cpp
+++ b/Source/WebCore/platform/graphics/MediaPlayer.cpp
@@ -1625,6 +1625,11 @@ void MediaPlayer::simulateAudioInterruption()
 
     m_private->simulateAudioInterruption();
 }
+
+bool MediaPlayer::isGStreamerHolePunchingEnabled()
+{
+    return client().isGStreamerHolePunchingEnabled();
+}
 #endif
 
 void MediaPlayer::beginSimulatedHDCPError()
diff --git a/Source/WebCore/platform/graphics/MediaPlayer.h b/Source/WebCore/platform/graphics/MediaPlayer.h
index a103b671c33c2..9ff5a4597feea 100644
--- a/Source/WebCore/platform/graphics/MediaPlayer.h
+++ b/Source/WebCore/platform/graphics/MediaPlayer.h
@@ -295,6 +295,8 @@ class MediaPlayerClient {
 
     virtual bool mediaPlayerShouldDisableHDR() const { return false; }
 
+    virtual bool isGStreamerHolePunchingEnabled() const { return false; }
+
 #if !RELEASE_LOG_DISABLED
     virtual const void* mediaPlayerLogIdentifier() { return nullptr; }
     virtual const Logger& mediaPlayerLogger() = 0;
@@ -620,6 +622,7 @@ class WEBCORE_EXPORT MediaPlayer : public MediaPlayerEnums, public ThreadSafeRef
 
 #if USE(GSTREAMER)
     void simulateAudioInterruption();
+    bool isGStreamerHolePunchingEnabled();
 #endif
 
     void beginSimulatedHDCPError();
diff --git a/Source/WebCore/platform/graphics/gstreamer/GStreamerCommon.cpp b/Source/WebCore/platform/graphics/gstreamer/GStreamerCommon.cpp
index 6a10233f154cb..1e2c6138b1e18 100644
--- a/Source/WebCore/platform/graphics/gstreamer/GStreamerCommon.cpp
+++ b/Source/WebCore/platform/graphics/gstreamer/GStreamerCommon.cpp
@@ -27,6 +27,7 @@
 #include "DMABufVideoSinkGStreamer.h"
 #include "GLVideoSinkGStreamer.h"
 #include "GStreamerAudioMixer.h"
+#include "GStreamerQuirks.h"
 #include "GStreamerRegistryScanner.h"
 #include "GStreamerSinksWorkarounds.h"
 #include "GUniquePtrGStreamer.h"
@@ -307,19 +308,6 @@ bool ensureGStreamerInitialized()
             gst_mpegts_initialize();
 #endif
 
-#if PLATFORM(BCM_NEXUS)
-        {
-            auto registry = gst_registry_get();
-            GRefPtr brcmaudfilter = adoptGRef(gst_registry_lookup_feature(registry, "brcmaudfilter"));
-            GRefPtr mpegaudioparse = adoptGRef(gst_registry_lookup_feature(registry, "mpegaudioparse"));
-
-            if (brcmaudfilter && mpegaudioparse) {
-                GST_INFO("overriding mpegaudioparse rank with brcmaudfilter rank + 1");
-                gst_plugin_feature_set_rank(mpegaudioparse.get(), gst_plugin_feature_get_rank(brcmaudfilter.get()) + 1);
-            }
-        }
-#endif
-
         registerAppsinkWithWorkaroundsIfNeeded();
 #endif
     });
@@ -421,6 +409,10 @@ void registerWebKitGStreamerElements()
             if (auto vaapiPlugin = adoptGRef(gst_registry_find_plugin(registry, "vaapi")))
                 gst_registry_remove_plugin(registry, vaapiPlugin.get());
         }
+
+        // Make sure the quirks are created as early as possible.
+        [[maybe_unused]] auto& quirksManager = GStreamerQuirksManager::singleton();
+
         registryWasUpdated = true;
     });
 
diff --git a/Source/WebCore/platform/graphics/gstreamer/GStreamerRegistryScanner.cpp b/Source/WebCore/platform/graphics/gstreamer/GStreamerRegistryScanner.cpp
index 55cb338448d91..dbea71ce146a2 100644
--- a/Source/WebCore/platform/graphics/gstreamer/GStreamerRegistryScanner.cpp
+++ b/Source/WebCore/platform/graphics/gstreamer/GStreamerRegistryScanner.cpp
@@ -19,6 +19,7 @@
 
 #include "config.h"
 #include "GStreamerRegistryScanner.h"
+#include "GStreamerQuirks.h"
 
 #if USE(GSTREAMER)
 #include "ContentType.h"
@@ -77,17 +78,12 @@ void GStreamerRegistryScanner::getSupportedDecodingTypes(HashSet types)
 {
-#if PLATFORM(BCM_NEXUS) || PLATFORM(BROADCOM)
+    auto& quirksManager = GStreamerQuirksManager::singleton();
+    auto audioVideoDecoderFactory = quirksManager.audioVideoDecoderFactoryListType();
     if (types.contains(Type::AudioDecoder))
-        audioDecoderFactories = gst_element_factory_list_get_elements(GST_ELEMENT_FACTORY_TYPE_PARSER | GST_ELEMENT_FACTORY_TYPE_MEDIA_AUDIO, GST_RANK_MARGINAL);
+        audioDecoderFactories = gst_element_factory_list_get_elements(audioVideoDecoderFactory | GST_ELEMENT_FACTORY_TYPE_MEDIA_AUDIO, GST_RANK_MARGINAL);
     if (types.contains(Type::VideoDecoder))
-        videoDecoderFactories = gst_element_factory_list_get_elements(GST_ELEMENT_FACTORY_TYPE_PARSER | GST_ELEMENT_FACTORY_TYPE_MEDIA_VIDEO, GST_RANK_MARGINAL);
-#else
-    if (types.contains(Type::AudioDecoder))
-        audioDecoderFactories = gst_element_factory_list_get_elements(GST_ELEMENT_FACTORY_TYPE_DECODER | GST_ELEMENT_FACTORY_TYPE_MEDIA_AUDIO, GST_RANK_MARGINAL);
-    if (types.contains(Type::VideoDecoder))
-        videoDecoderFactories = gst_element_factory_list_get_elements(GST_ELEMENT_FACTORY_TYPE_DECODER | GST_ELEMENT_FACTORY_TYPE_MEDIA_VIDEO, GST_RANK_MARGINAL);
-#endif
+        videoDecoderFactories = gst_element_factory_list_get_elements(audioVideoDecoderFactory | GST_ELEMENT_FACTORY_TYPE_MEDIA_VIDEO, GST_RANK_MARGINAL);
     if (types.contains(Type::AudioParser))
         audioParserFactories = gst_element_factory_list_get_elements(GST_ELEMENT_FACTORY_TYPE_PARSER | GST_ELEMENT_FACTORY_TYPE_MEDIA_AUDIO, GST_RANK_NONE);
     if (types.contains(Type::VideoParser))
@@ -236,15 +232,16 @@ GStreamerRegistryScanner::RegistryLookupResult GStreamerRegistryScanner::Element
             auto* factory = reinterpret_cast(factories->data);
             auto metadata = String::fromLatin1(gst_element_factory_get_metadata(factory, GST_ELEMENT_METADATA_KLASS));
             auto components = metadata.split('/');
-            if (components.contains("Hardware"_s)
-#if PLATFORM(BCM_NEXUS) || PLATFORM(BROADCOM)
-                || g_str_has_prefix(GST_OBJECT_NAME(factory), "brcm")
-#elif PLATFORM(REALTEK)
-                || g_str_has_prefix(GST_OBJECT_NAME(factory), "omx")
-#elif USE(WESTEROS_SINK)
-                || g_str_has_prefix(GST_OBJECT_NAME(factory), "westeros")
-#endif
-                ) {
+            auto& quirksManager = GStreamerQuirksManager::singleton();
+            if (quirksManager.isEnabled()) {
+                auto isAccelerated = quirksManager.isHardwareAccelerated(factory);
+                if (isAccelerated && *isAccelerated) {
+                    isUsingHardware = true;
+                    selectedFactory = factory;
+                    break;
+                }
+            }
+            if (components.contains("Hardware"_s)) {
                 isUsingHardware = true;
                 selectedFactory = factory;
                 break;
diff --git a/Source/WebCore/platform/graphics/gstreamer/MediaPlayerPrivateGStreamer.cpp b/Source/WebCore/platform/graphics/gstreamer/MediaPlayerPrivateGStreamer.cpp
index 9764a80f45cec..847492b8911dc 100644
--- a/Source/WebCore/platform/graphics/gstreamer/MediaPlayerPrivateGStreamer.cpp
+++ b/Source/WebCore/platform/graphics/gstreamer/MediaPlayerPrivateGStreamer.cpp
@@ -28,31 +28,33 @@
 
 #if ENABLE(VIDEO) && USE(GSTREAMER)
 
-#include "GraphicsContext.h"
+#include "AudioTrackPrivateGStreamer.h"
 #include "GStreamerAudioMixer.h"
 #include "GStreamerCommon.h"
+#include "GStreamerQuirks.h"
 #include "GStreamerRegistryScanner.h"
+#include "GraphicsContext.h"
 #include "HTTPHeaderNames.h"
 #include "ImageGStreamer.h"
 #include "ImageOrientation.h"
+#include "InbandMetadataTextTrackPrivateGStreamer.h"
+#include "InbandTextTrackPrivateGStreamer.h"
 #include "IntRect.h"
 #include "Logging.h"
 #include "MediaPlayer.h"
 #include "MediaPlayerRequestInstallMissingPluginsCallback.h"
 #include "MIMETypeRegistry.h"
+#include "MediaPlayer.h"
 #include "NotImplemented.h"
 #include "SecurityOrigin.h"
-#include "TimeRanges.h"
-#include "VideoSinkGStreamer.h"
-#include "WebKitAudioSinkGStreamer.h"
-#include "WebKitWebSourceGStreamer.h"
-#include "AudioTrackPrivateGStreamer.h"
-#include "InbandMetadataTextTrackPrivateGStreamer.h"
-#include "InbandTextTrackPrivateGStreamer.h"
 #include "TextCombinerGStreamer.h"
 #include "TextSinkGStreamer.h"
+#include "TimeRanges.h"
 #include "VideoFrameMetadataGStreamer.h"
+#include "VideoSinkGStreamer.h"
 #include "VideoTrackPrivateGStreamer.h"
+#include "WebKitAudioSinkGStreamer.h"
+#include "WebKitWebSourceGStreamer.h"
 
 #if ENABLE(MEDIA_STREAM)
 #include "GStreamerMediaStreamSource.h"
@@ -83,20 +85,24 @@
 #include 
 #include 
 #include 
-#include 
-#include 
 #include 
 #include 
 #include 
 #include 
 #include 
 #include 
+#include 
+#include 
+#include 
+#include 
+#include 
 #include 
 #include 
 #include 
 #include 
 #include 
 #include 
+#include 
 
 #if USE(GSTREAMER_MPEGTS)
 #define GST_USE_UNSTABLE_API
@@ -144,9 +150,7 @@ GST_DEBUG_CATEGORY(webkit_media_player_debug);
 namespace WebCore {
 using namespace std;
 
-#if USE(GSTREAMER_HOLEPUNCH)
 static const FloatSize s_holePunchDefaultFrameSize(1280, 720);
-#endif
 
 static void initializeDebugCategory()
 {
@@ -185,9 +189,17 @@ MediaPlayerPrivateGStreamer::MediaPlayerPrivateGStreamer(MediaPlayer* player)
 #endif
     m_isPlayerShuttingDown.store(false);
 
+    if (player->isGStreamerHolePunchingEnabled()) {
+        m_quirksManagerForTesting = GStreamerQuirksManager::createForTesting();
+        m_quirksManagerForTesting->setHolePunchEnabledForTesting(true);
+    }
+
 #if USE(TEXTURE_MAPPER_GL) && USE(NICOSIA)
     m_nicosiaLayer = Nicosia::ContentLayer::create(Nicosia::ContentLayerTextureMapperImpl::createFactory(*this,
         [&]() -> Ref {
+            if (isHolePunchRenderingEnabled())
+                return adoptRef(*new TextureMapperPlatformLayerProxyGL);
+
 #if USE(TEXTURE_MAPPER_DMABUF)
             if (webKitDMABufVideoSinkIsEnabled() && webKitDMABufVideoSinkProbePlatform())
                 return adoptRef(*new TextureMapperPlatformLayerProxyDMABuf);
@@ -211,10 +223,8 @@ MediaPlayerPrivateGStreamer::~MediaPlayerPrivateGStreamer()
     GST_DEBUG_OBJECT(pipeline(), "Disposing player");
     m_isPlayerShuttingDown.store(true);
 
-#if USE(GSTREAMER_HOLEPUNCH)
     if (m_gstreamerHolePunchHost)
         m_gstreamerHolePunchHost->playerPrivateWillBeDestroyed();
-#endif
 
     m_sinkTaskQueue.startAborting();
 
@@ -1008,7 +1018,7 @@ void MediaPlayerPrivateGStreamer::setPlaybinURL(const URL& url)
     g_object_set(m_pipeline.get(), "uri", m_url.string().utf8().data(), nullptr);
 }
 
-static void setSyncOnClock(GstElement *element, bool sync)
+static void setSyncOnClock(GstElement* element, bool sync)
 {
     if (!element)
         return;
@@ -1028,12 +1038,12 @@ static void setSyncOnClock(GstElement *element, bool sync)
 
 void MediaPlayerPrivateGStreamer::syncOnClock(bool sync)
 {
-#if !USE(WESTEROS_SINK)
+    auto& quirksManager = GStreamerQuirksManager::singleton();
+    if (quirksManager.supportsVideoHolePunchRendering() && !quirksManager.sinksRequireClockSynchronization())
+        return;
+
     setSyncOnClock(videoSink(), sync);
     setSyncOnClock(audioSink(), sync);
-#else
-    UNUSED_PARAM(sync);
-#endif
 }
 
 template 
@@ -1302,11 +1312,12 @@ void MediaPlayerPrivateGStreamer::loadingFailed(MediaPlayer::NetworkState networ
 
 GstElement* MediaPlayerPrivateGStreamer::createAudioSink()
 {
-#if PLATFORM(BROADCOM) || USE(WESTEROS_SINK) || PLATFORM(AMLOGIC) || PLATFORM(REALTEK)
+    auto& quirksManager = GStreamerQuirksManager::singleton();
+
     // If audio is being controlled by an another pipeline, creating sink here may interfere with
     // audio playback. Instead, check if an audio sink was setup in handleMessage and use it.
-    return nullptr;
-#endif
+    if (quirksManager.isEnabled())
+        return nullptr;
 
     // For platform specific audio sinks, they need to be properly upranked so that they get properly autoplugged.
 
@@ -1658,12 +1669,11 @@ FloatSize MediaPlayerPrivateGStreamer::naturalSize() const
     if (!m_videoSize.isEmpty())
         return m_videoSize;
 
-#if USE(GSTREAMER_HOLEPUNCH)
-    // When using the holepuch we may not be able to get the video frames size, so we can't use
+    // When using the holepunch we may not be able to get the video frames size, so we can't use
     // it. But we need to report some non empty naturalSize for the player's GraphicsLayer
     // to be properly created.
-    return s_holePunchDefaultFrameSize;
-#endif
+    if (isHolePunchRenderingEnabled())
+        return s_holePunchDefaultFrameSize;
 
     return m_videoSize;
 }
@@ -1892,8 +1902,7 @@ void MediaPlayerPrivateGStreamer::handleMessage(GstMessage* message)
         GstState newState;
         gst_message_parse_state_changed(message, ¤tState, &newState, nullptr);
 
-#if USE(GSTREAMER_HOLEPUNCH) && (USE(WPEWEBKIT_PLATFORM_BCM_NEXUS) || USE(WESTEROS_SINK))
-        if (currentState <= GST_STATE_READY && newState >= GST_STATE_READY) {
+        if (isHolePunchRenderingEnabled() && currentState <= GST_STATE_READY && newState >= GST_STATE_READY) {
             // If we didn't create a video sink, store a reference to the created one.
             if (!m_videoSink) {
                 // Detect the videoSink element. Getting the video-sink property of the pipeline requires
@@ -1911,10 +1920,9 @@ void MediaPlayerPrivateGStreamer::handleMessage(GstMessage* message)
                 }
             }
         }
-#endif
 
-#if PLATFORM(BROADCOM) || USE(WESTEROS_SINK) || PLATFORM(AMLOGIC) || PLATFORM(REALTEK)
-        if (currentState <= GST_STATE_READY && newState >= GST_STATE_READY) {
+        auto& quirksManager = GStreamerQuirksManager::singleton();
+        if (quirksManager.isEnabled() && currentState <= GST_STATE_READY && newState >= GST_STATE_READY) {
             // Detect an audio sink element and store reference to it if it supersedes what we currently have.
             GstElement* element = GST_ELEMENT(GST_MESSAGE_SRC(message));
             if (GST_OBJECT_FLAG_IS_SET(element, GST_ELEMENT_FLAG_SINK)) {
@@ -1924,7 +1932,6 @@ void MediaPlayerPrivateGStreamer::handleMessage(GstMessage* message)
                     m_audioSink = element;
             }
         }
-#endif
 
         if (!messageSourceIsPlaybin || m_isDelayingLoad)
             break;
@@ -2284,9 +2291,7 @@ void MediaPlayerPrivateGStreamer::processTableOfContentsEntry(GstTocEntry* entry
 
 void MediaPlayerPrivateGStreamer::configureElement(GstElement* element)
 {
-#if PLATFORM(BROADCOM) || USE(WESTEROS_SINK) || PLATFORM(AMLOGIC) || PLATFORM(REALTEK)
     configureElementPlatformQuirks(element);
-#endif
 
     GUniquePtr elementName(gst_element_get_name(element));
     auto elementClass = makeString(gst_element_get_metadata(element, GST_ELEMENT_METADATA_KLASS));
@@ -2322,58 +2327,22 @@ void MediaPlayerPrivateGStreamer::configureElement(GstElement* element)
         g_object_set(G_OBJECT(element), "high-watermark", 0.10, nullptr);
 }
 
-#if PLATFORM(BROADCOM) || USE(WESTEROS_SINK) || PLATFORM(AMLOGIC) || PLATFORM(REALTEK)
 void MediaPlayerPrivateGStreamer::configureElementPlatformQuirks(GstElement* element)
 {
     GST_DEBUG_OBJECT(pipeline(), "Element set-up for %s", GST_ELEMENT_NAME(element));
 
-#if PLATFORM(AMLOGIC)
-    if (!g_strcmp0(G_OBJECT_TYPE_NAME(G_OBJECT(element)), "GstAmlHalAsink")) {
-        GST_INFO_OBJECT(pipeline(), "Set property disable-xrun to TRUE");
-        g_object_set(element, "disable-xrun", TRUE, nullptr);
-        if (hasVideo())
-            g_object_set(G_OBJECT(element), "wait-video", TRUE, nullptr);
-    }
-#endif
-
-#if PLATFORM(BROADCOM)
-    if (g_str_has_prefix(GST_ELEMENT_NAME(element), "brcmaudiosink"))
-        g_object_set(G_OBJECT(element), "async", TRUE, nullptr);
-    else if (g_str_has_prefix(GST_ELEMENT_NAME(element), "brcmaudiodecoder")) {
-        if (m_isLiveStream.value_or(false)) {
-            // Limit BCM audio decoder buffering to 1sec so live progressive playback can start faster.
-            g_object_set(G_OBJECT(element), "limit_buffering_ms", 1000, nullptr);
-        }
-    }
-#if ENABLE(MEDIA_STREAM)
-    if (m_streamPrivate && !g_strcmp0(G_OBJECT_TYPE_NAME(G_OBJECT(element)), "GstBrcmPCMSink") && gstObjectHasProperty(element, "low_latency")) {
-        GST_DEBUG_OBJECT(pipeline(), "Set 'low_latency' in brcmpcmsink");
-        g_object_set(element, "low_latency", TRUE, "low_latency_max_queued_ms", 60, nullptr);
-    }
-#endif
-#endif
-
-#if ENABLE(MEDIA_STREAM)
-    if (m_streamPrivate && !g_strcmp0(G_OBJECT_TYPE_NAME(G_OBJECT(element)), "GstWesterosSink") && gstObjectHasProperty(element, "immediate-output")) {
-        GST_DEBUG_OBJECT(pipeline(), "Enable 'immediate-output' in WesterosSink");
-        g_object_set(element, "immediate-output", TRUE, nullptr);
-    }
-#endif
+    OptionSet characteristics;
+    if (isMediaStreamPlayer())
+        characteristics.add({ ElementRuntimeCharacteristics::IsMediaStream });
+    if (hasVideo())
+        characteristics.add({ ElementRuntimeCharacteristics::HasVideo });
+    if (hasAudio())
+        characteristics.add({ ElementRuntimeCharacteristics::HasAudio });
+    if (m_isLiveStream.value_or(false))
+        characteristics.add({ ElementRuntimeCharacteristics::IsLiveStream });
 
-#if ENABLE(MEDIA_STREAM) && PLATFORM(REALTEK)
-    if (m_streamPrivate) {
-        if (gstObjectHasProperty(element, "media-tunnel")) {
-            GST_INFO_OBJECT(pipeline(), "Enable 'immediate-output' in rtkaudiosink");
-            g_object_set(element, "media-tunnel", FALSE, "audio-service", TRUE, "lowdelay-sync-mode", TRUE, nullptr);
-        }
-        if (gstObjectHasProperty(element, "lowdelay-mode")) {
-            GST_INFO_OBJECT(pipeline(), "Enable 'lowdelay-mode' in rtk omx decoder");
-            g_object_set(element, "lowdelay-mode", TRUE, nullptr);
-        }
-    }
-#endif
+    GStreamerQuirksManager::singleton().configureElement(element, WTFMove(characteristics));
 }
-#endif
 
 void MediaPlayerPrivateGStreamer::configureDownloadBuffer(GstElement* element)
 {
@@ -2925,19 +2894,11 @@ void MediaPlayerPrivateGStreamer::setPlaybackFlags(bool isMediaStream)
     if (isMediaStream)
         flags = flags & ~getGstPlayFlag("buffering");
 
-#if !USE(GSTREAMER_TEXT_SINK)
-    hasText = 0x0;
-#endif
-
-#if USE(GSTREAMER_NATIVE_VIDEO)
-    hasSoftwareColorBalance = 0x0;
-#else
-    hasNativeVideo = 0x0;
-#endif
-
-#if !USE(GSTREAMER_NATIVE_AUDIO)
-    hasNativeAudio = 0x0;
-#endif
+    unsigned additionalFlags = GStreamerQuirksManager::singleton().getAdditionalPlaybinFlags();
+    hasText &= additionalFlags;
+    hasSoftwareColorBalance &= additionalFlags;
+    hasNativeVideo &= additionalFlags;
+    hasNativeAudio &= additionalFlags;
 
     GST_INFO_OBJECT(pipeline(), "text %s, audio %s (native %s), video %s (native %s, software color balance %s)", boolForPrinting(hasText),
         boolForPrinting(hasAudio), boolForPrinting(hasNativeAudio), boolForPrinting(hasVideo), boolForPrinting(hasNativeVideo),
@@ -3037,19 +2998,14 @@ void MediaPlayerPrivateGStreamer::createGSTPlayBin(const URL& url)
     if (!m_player->isVideoPlayer())
         return;
 
-#if !USE(GSTREAMER_HOLEPUNCH)
+    if (isHolePunchRenderingEnabled())
+        return;
+
     GRefPtr videoSinkPad = adoptGRef(gst_element_get_static_pad(m_videoSink.get(), "sink"));
     if (videoSinkPad)
         g_signal_connect(videoSinkPad.get(), "notify::caps", G_CALLBACK(+[](GstPad* videoSinkPad, GParamSpec*, MediaPlayerPrivateGStreamer* player) {
             player->videoSinkCapsChanged(videoSinkPad);
         }), this);
-#endif
-
-#if USE(WESTEROS_SINK)
-    // Configure Westeros sink before it allocates resources.
-    if (m_videoSink)
-        configureElementPlatformQuirks(m_videoSink.get());
-#endif
 }
 
 void MediaPlayerPrivateGStreamer::configureVideoDecoder(GstElement* decoder)
@@ -3184,9 +3140,8 @@ PlatformLayer* MediaPlayerPrivateGStreamer::platformLayer() const
 #if USE(NICOSIA)
 void MediaPlayerPrivateGStreamer::swapBuffersIfNeeded()
 {
-#if USE(GSTREAMER_HOLEPUNCH)
-    pushNextHolePunchBuffer();
-#endif
+    if (isHolePunchRenderingEnabled())
+        pushNextHolePunchBuffer();
 }
 #else
 RefPtr MediaPlayerPrivateGStreamer::proxy() const
@@ -3196,9 +3151,8 @@ RefPtr MediaPlayerPrivateGStreamer::proxy() con
 
 void MediaPlayerPrivateGStreamer::swapBuffersIfNeeded()
 {
-#if USE(GSTREAMER_HOLEPUNCH)
-    pushNextHolePunchBuffer();
-#endif
+    if (isHolePunchRenderingEnabled())
+        pushNextHolePunchBuffer();
 }
 #endif
 
@@ -4059,23 +4013,21 @@ GstElement* MediaPlayerPrivateGStreamer::createVideoSinkGL()
 }
 #endif // USE(GSTREAMER_GL)
 
-#if USE(GSTREAMER_HOLEPUNCH)
-static void setRectangleToVideoSink(GstElement* videoSink, const IntRect& rect)
+static void setRectangleToVideoSink(GStreamerQuirksManager* quirksManagerForTesting, GstElement* videoSink, const IntRect& rect)
 {
     // Here goes the platform-dependant code to set to the videoSink the size
-    // and position of the video rendering window. Mark them unused as default.
+    // and position of the video rendering window.
 
     if (!videoSink)
         return;
 
-#if USE(WESTEROS_SINK) || USE(WPEWEBKIT_PLATFORM_BCM_NEXUS)
-    // Valid for brcmvideosink and westerossink.
-    GUniquePtr rectString(g_strdup_printf("%d,%d,%d,%d", rect.x(), rect.y(), rect.width(), rect.height()));
-    g_object_set(videoSink, "rectangle", rectString.get(), nullptr);
-    return;
-#endif
+    if (quirksManagerForTesting) {
+        quirksManagerForTesting->setHolePunchVideoRectangle(videoSink, rect);
+        return;
+    }
 
-    UNUSED_PARAM(rect);
+    auto& quirksManager = GStreamerQuirksManager::singleton();
+    quirksManager.setHolePunchVideoRectangle(videoSink, rect);
 }
 
 class GStreamerHolePunchClient : public TextureMapperPlatformLayerBuffer::HolePunchClient {
@@ -4086,31 +4038,29 @@ class GStreamerHolePunchClient : public TextureMapperPlatformLayerBuffer::HolePu
     RefPtr m_host;
 };
 
-GstElement* MediaPlayerPrivateGStreamer::createHolePunchVideoSink()
+bool MediaPlayerPrivateGStreamer::isHolePunchRenderingEnabled() const
 {
-    // Here goes the platform-dependant code to create the videoSink. As a default
-    // we use a fakeVideoSink so nothing is drawn to the page.
+    if (m_quirksManagerForTesting)
+        return m_quirksManagerForTesting->supportsVideoHolePunchRendering();
+    auto& quirksManager = GStreamerQuirksManager::singleton();
+    return quirksManager.supportsVideoHolePunchRendering();
+}
 
-#if USE(WESTEROS_SINK)
-    AtomString val;
-    bool isPIPRequested =
-        m_player->doesHaveAttribute("pip"_s, &val) && equalLettersIgnoringASCIICase(val, "true"_s);
-    if (m_isLegacyPlaybin && !isPIPRequested)
+GstElement* MediaPlayerPrivateGStreamer::createHolePunchVideoSink()
+{
+    if (!isHolePunchRenderingEnabled())
         return nullptr;
-    // Westeros using holepunch.
-    GstElement* videoSink = makeGStreamerElement("westerossink", "WesterosVideoSink");
-    g_object_set(G_OBJECT(videoSink), "zorder", 0.0f, nullptr);
-    if (isPIPRequested)
-        g_object_set(G_OBJECT(videoSink), "res-usage", 0u, nullptr);
-    return videoSink;
-#endif
 
-#if USE(WPEWEBKIT_PLATFORM_BCM_NEXUS)
-    // Nexus boxes use autovideosink.
-    return nullptr;
-#endif
+    GstElement* sink = nullptr;
+    if (m_quirksManagerForTesting)
+        sink = m_quirksManagerForTesting->createHolePunchVideoSink(m_isLegacyPlaybin, m_player);
+    else
+        sink = GStreamerQuirksManager::singleton().createHolePunchVideoSink(m_isLegacyPlaybin, m_player);
 
-    return makeGStreamerElement("fakevideosink", nullptr);
+    // Configure sink before it allocates resources.
+    if (sink)
+        configureElement(sink);
+    return sink;
 }
 
 void MediaPlayerPrivateGStreamer::pushNextHolePunchBuffer()
@@ -4128,6 +4078,7 @@ void MediaPlayerPrivateGStreamer::pushNextHolePunchBuffer()
             proxy.pushNextBuffer(WTFMove(layerBuffer));
         };
 
+    ASSERT(isHolePunchRenderingEnabled());
 #if USE(NICOSIA)
     auto& proxy = downcast(m_nicosiaLayer->impl()).proxy();
     ASSERT(is(proxy));
@@ -4142,9 +4093,13 @@ void MediaPlayerPrivateGStreamer::setVideoRectangle(const IntRect& rect)
     Locker locker { m_holePunchLock };
 
     if (m_visible && !m_suspended)
-        setRectangleToVideoSink(m_videoSink.get(), rect);
+        setRectangleToVideoSink(m_quirksManagerForTesting.get(), m_videoSink.get(), rect);
+}
+
+bool MediaPlayerPrivateGStreamer::shouldIgnoreIntrinsicSize()
+{
+    return isHolePunchRenderingEnabled();
 }
-#endif
 
 GstElement* MediaPlayerPrivateGStreamer::createVideoSink()
 {
@@ -4180,11 +4135,12 @@ GstElement* MediaPlayerPrivateGStreamer::createVideoSink()
         return m_videoSink.get();
     }
 
-#if USE(GSTREAMER_HOLEPUNCH)
-    m_videoSink = createHolePunchVideoSink();
-    pushNextHolePunchBuffer();
-    return m_videoSink.get();
-#endif
+    if (isHolePunchRenderingEnabled()) {
+        m_videoSink = createHolePunchVideoSink();
+        // Do not check the m_videoSink value. The nullptr case will trigger auto-plugging in playbin.
+        pushNextHolePunchBuffer();
+        return m_videoSink.get();
+    }
 
 #if USE(TEXTURE_MAPPER_DMABUF)
     if (!m_videoSink && m_canRenderingBeAccelerated)
@@ -4508,32 +4464,25 @@ std::optional MediaPlayerPrivateGStreamer::videoFrameMetadat
 
 void MediaPlayerPrivateGStreamer::setPageIsVisible(bool visible)
 {
-
     if (m_visible != visible) {
-#if USE(GSTREAMER_HOLEPUNCH)
         Locker locker { m_holePunchLock };
-#endif
+
         m_visible = visible;
 
-#if USE(GSTREAMER_HOLEPUNCH)
         if (!m_visible)
-            setRectangleToVideoSink(m_videoSink.get(), IntRect());
-#endif
+            setRectangleToVideoSink(m_quirksManagerForTesting.get(), m_videoSink.get(), IntRect());
     }
 }
 
 void MediaPlayerPrivateGStreamer::setPageIsSuspended(bool suspended)
 {
     if (m_suspended != suspended) {
-#if USE(GSTREAMER_HOLEPUNCH)
         Locker locker { m_holePunchLock };
-#endif
+
         m_suspended = suspended;
 
-#if USE(GSTREAMER_HOLEPUNCH)
         if (m_suspended)
-            setRectangleToVideoSink(m_videoSink.get(), IntRect());
-#endif
+            setRectangleToVideoSink(m_quirksManagerForTesting.get(), m_videoSink.get(), IntRect());
     }
 }
 
diff --git a/Source/WebCore/platform/graphics/gstreamer/MediaPlayerPrivateGStreamer.h b/Source/WebCore/platform/graphics/gstreamer/MediaPlayerPrivateGStreamer.h
index ac361a379b610..c741675edef59 100644
--- a/Source/WebCore/platform/graphics/gstreamer/MediaPlayerPrivateGStreamer.h
+++ b/Source/WebCore/platform/graphics/gstreamer/MediaPlayerPrivateGStreamer.h
@@ -29,6 +29,7 @@
 #include "AbortableTaskQueue.h"
 #include "GStreamerCommon.h"
 #include "GStreamerEMEUtilities.h"
+#include "GStreamerQuirks.h"
 #include "ImageOrientation.h"
 #include "Logging.h"
 #include "MainThreadNotifier.h"
@@ -196,6 +197,8 @@ class MediaPlayerPrivateGStreamer : public MediaPlayerPrivateInterface
     void acceleratedRenderingStateChanged() final;
     bool performTaskAtMediaTime(Function&&, const MediaTime&) override;
 
+    GstElement* pipeline() const { return m_pipeline.get(); }
+
 #if USE(TEXTURE_MAPPER_GL)
     PlatformLayer* platformLayer() const override;
 #if PLATFORM(WIN_CAIRO)
@@ -254,7 +257,6 @@ class MediaPlayerPrivateGStreamer : public MediaPlayerPrivateInterface
     // to avoid deadlocks from threads in the playback pipeline waiting for the main thread.
     AbortableTaskQueue& sinkTaskQueue() { return m_sinkTaskQueue; }
 
-#if USE(GSTREAMER_HOLEPUNCH)
     class GStreamerHolePunchHost : public ThreadSafeRefCounted {
     public:
         static Ref create(MediaPlayerPrivateGStreamer& playerPrivate)
@@ -277,7 +279,6 @@ class MediaPlayerPrivateGStreamer : public MediaPlayerPrivateInterface
         MediaPlayerPrivateGStreamer* m_playerPrivate;
     };
     void setVideoRectangle(const IntRect& rect);
-#endif
 
 protected:
     enum MainThreadNotification {
@@ -313,11 +314,10 @@ class MediaPlayerPrivateGStreamer : public MediaPlayerPrivateInterface
     virtual void sourceSetup(GstElement*);
     virtual void updatePlaybackRate();
 
-#if USE(GSTREAMER_HOLEPUNCH)
+    bool isHolePunchRenderingEnabled() const;
     GstElement* createHolePunchVideoSink();
     void pushNextHolePunchBuffer();
-    bool shouldIgnoreIntrinsicSize() final { return true; }
-#endif
+    bool shouldIgnoreIntrinsicSize() final;
 
 #if USE(TEXTURE_MAPPER_DMABUF)
     GstElement* createVideoSinkDMABuf();
@@ -344,8 +344,6 @@ class MediaPlayerPrivateGStreamer : public MediaPlayerPrivateInterface
 
     void setStreamVolumeElement(GstStreamVolume*);
 
-    GstElement* pipeline() const { return m_pipeline.get(); }
-
     void repaint();
     void cancelRepaint(bool destroying = false);
 
@@ -543,9 +541,8 @@ class MediaPlayerPrivateGStreamer : public MediaPlayerPrivateInterface
 
     void configureVideoDecoder(GstElement*);
     void configureElement(GstElement*);
-#if PLATFORM(BROADCOM) || USE(WESTEROS_SINK) || PLATFORM(AMLOGIC) || PLATFORM(REALTEK)
+
     void configureElementPlatformQuirks(GstElement*);
-#endif
 
     void setPlaybinURL(const URL& urlString);
 
@@ -669,16 +666,16 @@ class MediaPlayerPrivateGStreamer : public MediaPlayerPrivateInterface
 
     AbortableTaskQueue m_sinkTaskQueue;
 
-#if USE(GSTREAMER_HOLEPUNCH)
     RefPtr m_gstreamerHolePunchHost;
     Lock m_holePunchLock;
-#endif
 
     bool m_didTryToRecoverPlayingState { false };
 
     // Specific to MediaStream playback.
     MediaTime m_startTime;
     MediaTime m_pausedTime;
+
+    RefPtr m_quirksManagerForTesting;
 };
 
 }
diff --git a/Source/WebCore/platform/gstreamer/GStreamerHolePunchQuirkBcmNexus.cpp b/Source/WebCore/platform/gstreamer/GStreamerHolePunchQuirkBcmNexus.cpp
new file mode 100644
index 0000000000000..37e3a70c4621c
--- /dev/null
+++ b/Source/WebCore/platform/gstreamer/GStreamerHolePunchQuirkBcmNexus.cpp
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2024 Igalia S.L
+ * Copyright (C) 2024 Metrological Group B.V.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public License
+ * aint with this library; see the file COPYING.LIB.  If not, write to
+ * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#include "config.h"
+#include "GStreamerHolePunchQuirkBcmNexus.h"
+
+#include "GStreamerCommon.h"
+
+#if USE(GSTREAMER)
+
+namespace WebCore {
+
+bool GStreamerHolePunchQuirkBcmNexus::setHolePunchVideoRectangle(GstElement* videoSink, const IntRect& rect)
+{
+    if (UNLIKELY(!gstObjectHasProperty(videoSink, "rectangle")))
+        return false;
+
+    auto rectString = makeString(rect.x(), ',', rect.y(), ',', rect.width(), ',', rect.height());
+    g_object_set(videoSink, "rectangle", rectString.ascii().data(), nullptr);
+    return true;
+}
+
+} // namespace WebCore
+
+#endif // USE(GSTREAMER)
diff --git a/Source/WebCore/platform/gstreamer/GStreamerHolePunchQuirkBcmNexus.h b/Source/WebCore/platform/gstreamer/GStreamerHolePunchQuirkBcmNexus.h
new file mode 100644
index 0000000000000..71e6e5e042178
--- /dev/null
+++ b/Source/WebCore/platform/gstreamer/GStreamerHolePunchQuirkBcmNexus.h
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2024 Igalia S.L
+ * Copyright (C) 2024 Metrological Group B.V.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public License
+ * aint with this library; see the file COPYING.LIB.  If not, write to
+ * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#pragma once
+
+#if USE(GSTREAMER)
+
+#include "GStreamerQuirks.h"
+
+namespace WebCore {
+
+class GStreamerHolePunchQuirkBcmNexus final : public GStreamerHolePunchQuirk {
+public:
+    const char* identifier() final { return "BcmNexusHolePunch"; }
+
+    // NOTE: We don't override createHolePunchVideoSink here because autovideosink takes care of
+    // auto-plugging the right sink.
+    bool setHolePunchVideoRectangle(GstElement*, const IntRect&) final;
+};
+
+} // namespace WebCore
+
+#endif // USE(GSTREAMER)
diff --git a/Source/WebCore/platform/gstreamer/GStreamerHolePunchQuirkFake.h b/Source/WebCore/platform/gstreamer/GStreamerHolePunchQuirkFake.h
new file mode 100644
index 0000000000000..3790f477151db
--- /dev/null
+++ b/Source/WebCore/platform/gstreamer/GStreamerHolePunchQuirkFake.h
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2024 Igalia S.L
+ * Copyright (C) 2024 Metrological Group B.V.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public License
+ * aint with this library; see the file COPYING.LIB.  If not, write to
+ * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#pragma once
+
+#if USE(GSTREAMER)
+
+#include "GStreamerCommon.h"
+#include "GStreamerQuirks.h"
+
+namespace WebCore {
+
+class GStreamerHolePunchQuirkFake final : public GStreamerHolePunchQuirk {
+public:
+    const char* identifier() final { return "FakeHolePunch"; }
+    GstElement* createHolePunchVideoSink(bool, const MediaPlayer*) final { return makeGStreamerElement("fakevideosink", nullptr); }
+    bool setHolePunchVideoRectangle(GstElement*, const IntRect&) final { return true; }
+};
+
+} // namespace WebCore
+
+#endif // USE(GSTREAMER)
diff --git a/Source/WebCore/platform/gstreamer/GStreamerHolePunchQuirkWesteros.cpp b/Source/WebCore/platform/gstreamer/GStreamerHolePunchQuirkWesteros.cpp
new file mode 100644
index 0000000000000..0fd94427142e3
--- /dev/null
+++ b/Source/WebCore/platform/gstreamer/GStreamerHolePunchQuirkWesteros.cpp
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2024 Igalia S.L
+ * Copyright (C) 2024 Metrological Group B.V.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public License
+ * aint with this library; see the file COPYING.LIB.  If not, write to
+ * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#include "config.h"
+#include "GStreamerHolePunchQuirkWesteros.h"
+#include "MediaPlayerPrivateGStreamer.h"
+
+#if USE(GSTREAMER)
+
+#include "GStreamerCommon.h"
+
+namespace WebCore {
+
+GstElement* GStreamerHolePunchQuirkWesteros::createHolePunchVideoSink(bool isLegacyPlaybin, const MediaPlayer* player)
+{
+    AtomString val;
+    bool isPIPRequested = player && player->doesHaveAttribute("pip"_s, &val) && equalLettersIgnoringASCIICase(val, "true"_s);
+    if (isLegacyPlaybin && !isPIPRequested)
+        return nullptr;
+
+    // Westeros using holepunch.
+    GstElement* videoSink = makeGStreamerElement("westerossink", "WesterosVideoSink");
+    g_object_set(videoSink, "zorder", 0.0f, nullptr);
+    if (isPIPRequested) {
+        g_object_set(videoSink, "res-usage", 0u, nullptr);
+        // Set context for pipelines that use ERM in decoder elements.
+        auto context = adoptGRef(gst_context_new("erm", FALSE));
+        auto contextStructure = gst_context_writable_structure(context.get());
+        gst_structure_set(contextStructure, "res-usage", G_TYPE_UINT, 0x0u, nullptr);
+        auto playerPrivate = reinterpret_cast(player->playerPrivate());
+        gst_element_set_context(playerPrivate->pipeline(), context.get());
+    }
+    return videoSink;
+}
+
+bool GStreamerHolePunchQuirkWesteros::setHolePunchVideoRectangle(GstElement* videoSink, const IntRect& rect)
+{
+    if (UNLIKELY(!gstObjectHasProperty(videoSink, "rectangle")))
+        return false;
+
+    auto rectString = makeString(rect.x(), ',', rect.y(), ',', rect.width(), ',', rect.height());
+    g_object_set(videoSink, "rectangle", rectString.ascii().data(), nullptr);
+    return true;
+}
+
+#undef GST_CAT_DEFAULT
+
+} // namespace WebCore
+
+#endif // USE(GSTREAMER)
diff --git a/Source/WebCore/platform/gstreamer/GStreamerHolePunchQuirkWesteros.h b/Source/WebCore/platform/gstreamer/GStreamerHolePunchQuirkWesteros.h
new file mode 100644
index 0000000000000..11f0ed9731a93
--- /dev/null
+++ b/Source/WebCore/platform/gstreamer/GStreamerHolePunchQuirkWesteros.h
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2024 Igalia S.L
+ * Copyright (C) 2024 Metrological Group B.V.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public License
+ * aint with this library; see the file COPYING.LIB.  If not, write to
+ * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#pragma once
+
+#if USE(GSTREAMER)
+
+#include "GStreamerQuirks.h"
+
+namespace WebCore {
+
+class GStreamerHolePunchQuirkWesteros final : public GStreamerHolePunchQuirk {
+public:
+    const char* identifier() final { return "WesterosHolePunch"; }
+
+    GstElement* createHolePunchVideoSink(bool, const MediaPlayer*) final;
+    bool setHolePunchVideoRectangle(GstElement*, const IntRect&) final;
+    bool requiresClockSynchronization() const final { return false; }
+};
+
+} // namespace WebCore
+
+#endif // USE(GSTREAMER)
diff --git a/Source/WebCore/platform/gstreamer/GStreamerQuirkAmLogic.cpp b/Source/WebCore/platform/gstreamer/GStreamerQuirkAmLogic.cpp
new file mode 100644
index 0000000000000..610f026e434d1
--- /dev/null
+++ b/Source/WebCore/platform/gstreamer/GStreamerQuirkAmLogic.cpp
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2024 Igalia S.L
+ * Copyright (C) 2024 Metrological Group B.V.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public License
+ * aint with this library; see the file COPYING.LIB.  If not, write to
+ * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#include "config.h"
+#include "GStreamerQuirkAmLogic.h"
+
+#if USE(GSTREAMER)
+
+#include "GStreamerCommon.h"
+#include 
+
+namespace WebCore {
+
+GST_DEBUG_CATEGORY_STATIC(webkit_amlogic_quirks_debug);
+#define GST_CAT_DEFAULT webkit_amlogic_quirks_debug
+
+GStreamerQuirkAmLogic::GStreamerQuirkAmLogic()
+{
+    GST_DEBUG_CATEGORY_INIT(webkit_amlogic_quirks_debug, "webkitquirksamlogic", 0, "WebKit AmLogic Quirks");
+}
+
+GstElement* GStreamerQuirkAmLogic::createWebAudioSink()
+{
+    // autoaudiosink changes child element state to READY internally in auto detection phase
+    // that causes resource acquisition in some cases interrupting any playback already running.
+    // On Amlogic we need to set direct-mode=false prop before changing state to READY
+    // but this is not possible with autoaudiosink.
+    auto sink = makeGStreamerElement("amlhalasink", nullptr);
+    RELEASE_ASSERT_WITH_MESSAGE(sink, "amlhalasink should be available in the system but it is not");
+    g_object_set(sink, "direct-mode", FALSE, nullptr);
+    return sink;
+}
+
+void GStreamerQuirkAmLogic::configureElement(GstElement* element, const OptionSet& characteristics)
+{
+    if (gstObjectHasProperty(element, "disable-xrun")) {
+        GST_INFO("Set property disable-xrun to TRUE");
+        g_object_set(element, "disable-xrun", TRUE, nullptr);
+    }
+
+    if (characteristics.contains(ElementRuntimeCharacteristics::HasVideo) && gstObjectHasProperty(element, "wait-video")) {
+        GST_INFO("Set property wait-video to TRUE");
+        g_object_set(element, "wait-video", TRUE, nullptr);
+    }
+}
+
+#undef GST_CAT_DEFAULT
+
+} // namespace WebCore
+
+#endif // USE(GSTREAMER)
diff --git a/Source/WebCore/platform/gstreamer/GStreamerQuirkAmLogic.h b/Source/WebCore/platform/gstreamer/GStreamerQuirkAmLogic.h
new file mode 100644
index 0000000000000..ee3662eb20586
--- /dev/null
+++ b/Source/WebCore/platform/gstreamer/GStreamerQuirkAmLogic.h
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2024 Igalia S.L
+ * Copyright (C) 2024 Metrological Group B.V.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public License
+ * aint with this library; see the file COPYING.LIB.  If not, write to
+ * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#pragma once
+
+#if USE(GSTREAMER)
+
+#include "GStreamerQuirks.h"
+
+namespace WebCore {
+
+class GStreamerQuirkAmLogic final : public GStreamerQuirk {
+public:
+    GStreamerQuirkAmLogic();
+    const char* identifier() final { return "AmLogic"; }
+
+    GstElement* createWebAudioSink() final;
+    void configureElement(GstElement*, const OptionSet&) final;
+};
+
+} // namespace WebCore
+
+#endif // USE(GSTREAMER)
diff --git a/Source/WebCore/platform/gstreamer/GStreamerQuirkBcmNexus.cpp b/Source/WebCore/platform/gstreamer/GStreamerQuirkBcmNexus.cpp
new file mode 100644
index 0000000000000..b0f3e2a57bf1e
--- /dev/null
+++ b/Source/WebCore/platform/gstreamer/GStreamerQuirkBcmNexus.cpp
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2024 Igalia S.L
+ * Copyright (C) 2024 Metrological Group B.V.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public License
+ * aint with this library; see the file COPYING.LIB.  If not, write to
+ * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#include "config.h"
+#include "GStreamerQuirkBcmNexus.h"
+
+#if USE(GSTREAMER)
+
+#include "GStreamerCommon.h"
+#include 
+
+namespace WebCore {
+
+GST_DEBUG_CATEGORY_STATIC(webkit_bcmnexus_quirks_debug);
+#define GST_CAT_DEFAULT webkit_bcmnexus_quirks_debug
+
+GStreamerQuirkBcmNexus::GStreamerQuirkBcmNexus()
+{
+    GST_DEBUG_CATEGORY_INIT(webkit_bcmnexus_quirks_debug, "webkitquirksbcmnexus", 0, "WebKit BcmNexus Quirks");
+    m_disallowedWebAudioDecoders = { "brcmaudfilter"_s };
+
+    auto registry = gst_registry_get();
+    auto brcmaudfilter = adoptGRef(gst_registry_lookup_feature(registry, "brcmaudfilter"));
+    auto mpegaudioparse = adoptGRef(gst_registry_lookup_feature(registry, "mpegaudioparse"));
+
+    if (brcmaudfilter && mpegaudioparse) {
+        GST_INFO("Overriding mpegaudioparse rank with brcmaudfilter rank + 1");
+        gst_plugin_feature_set_rank(mpegaudioparse.get(), gst_plugin_feature_get_rank(brcmaudfilter.get()) + 1);
+    }
+}
+
+std::optional GStreamerQuirkBcmNexus::isHardwareAccelerated(GstElementFactory* factory)
+{
+    if (g_str_has_prefix(GST_OBJECT_NAME(factory), "brcm"))
+        return true;
+
+    return std::nullopt;
+}
+
+#undef GST_CAT_DEFAULT
+
+} // namespace WebCore
+
+#endif // USE(GSTREAMER)
diff --git a/Source/WebCore/platform/gstreamer/GStreamerQuirkBcmNexus.h b/Source/WebCore/platform/gstreamer/GStreamerQuirkBcmNexus.h
new file mode 100644
index 0000000000000..e3987e80f4db1
--- /dev/null
+++ b/Source/WebCore/platform/gstreamer/GStreamerQuirkBcmNexus.h
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2024 Igalia S.L
+ * Copyright (C) 2024 Metrological Group B.V.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public License
+ * aint with this library; see the file COPYING.LIB.  If not, write to
+ * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#pragma once
+
+#if USE(GSTREAMER)
+
+#include "GStreamerQuirks.h"
+
+namespace WebCore {
+
+class GStreamerQuirkBcmNexus final : public GStreamerQuirk {
+public:
+    GStreamerQuirkBcmNexus();
+    const char* identifier() final { return "BcmNexus"; }
+
+    std::optional isHardwareAccelerated(GstElementFactory*) final;
+    std::optional audioVideoDecoderFactoryListType() const final { return GST_ELEMENT_FACTORY_TYPE_PARSER; }
+    Vector disallowedWebAudioDecoders() const final { return m_disallowedWebAudioDecoders; }
+
+private:
+    Vector m_disallowedWebAudioDecoders;
+};
+
+} // namespace WebCore
+
+#endif // USE(GSTREAMER)
diff --git a/Source/WebCore/platform/gstreamer/GStreamerQuirkBroadcom.cpp b/Source/WebCore/platform/gstreamer/GStreamerQuirkBroadcom.cpp
new file mode 100644
index 0000000000000..01738d0e220e1
--- /dev/null
+++ b/Source/WebCore/platform/gstreamer/GStreamerQuirkBroadcom.cpp
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2024 Igalia S.L
+ * Copyright (C) 2024 Metrological Group B.V.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public License
+ * aint with this library; see the file COPYING.LIB.  If not, write to
+ * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#include "config.h"
+#include "GStreamerQuirkBroadcom.h"
+
+#if USE(GSTREAMER)
+
+#include "GStreamerCommon.h"
+#include 
+
+namespace WebCore {
+
+GST_DEBUG_CATEGORY_STATIC(webkit_broadcom_quirks_debug);
+#define GST_CAT_DEFAULT webkit_broadcom_quirks_debug
+
+GStreamerQuirkBroadcom::GStreamerQuirkBroadcom()
+{
+    GST_DEBUG_CATEGORY_INIT(webkit_broadcom_quirks_debug, "webkitquirksbroadcom", 0, "WebKit Broadcom Quirks");
+    m_disallowedWebAudioDecoders = { "brcmaudfilter"_s };
+}
+
+void GStreamerQuirkBroadcom::configureElement(GstElement* element, const OptionSet& characteristics)
+{
+    if (g_str_has_prefix(GST_ELEMENT_NAME(element), "brcmaudiosink"))
+        g_object_set(G_OBJECT(element), "async", TRUE, nullptr);
+    else if (g_str_has_prefix(GST_ELEMENT_NAME(element), "brcmaudiodecoder")) {
+        // Limit BCM audio decoder buffering to 1sec so live progressive playback can start faster.
+        if (characteristics.contains(ElementRuntimeCharacteristics::IsLiveStream))
+            g_object_set(G_OBJECT(element), "limit_buffering_ms", 1000, nullptr);
+    }
+
+    if (!characteristics.contains(ElementRuntimeCharacteristics::IsMediaStream))
+        return;
+
+    if (!g_strcmp0(G_OBJECT_TYPE_NAME(G_OBJECT(element)), "GstBrcmPCMSink") && gstObjectHasProperty(element, "low_latency")) {
+        GST_DEBUG("Set 'low_latency' in brcmpcmsink");
+        g_object_set(element, "low_latency", TRUE, "low_latency_max_queued_ms", 60, nullptr);
+    }
+}
+
+std::optional GStreamerQuirkBroadcom::isHardwareAccelerated(GstElementFactory* factory)
+{
+    if (g_str_has_prefix(GST_OBJECT_NAME(factory), "brcm"))
+        return true;
+
+    return std::nullopt;
+}
+
+#undef GST_CAT_DEFAULT
+
+} // namespace WebCore
+
+#endif // USE(GSTREAMER)
diff --git a/Source/WebCore/platform/gstreamer/GStreamerQuirkBroadcom.h b/Source/WebCore/platform/gstreamer/GStreamerQuirkBroadcom.h
new file mode 100644
index 0000000000000..308a9d3a87961
--- /dev/null
+++ b/Source/WebCore/platform/gstreamer/GStreamerQuirkBroadcom.h
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2024 Igalia S.L
+ * Copyright (C) 2024 Metrological Group B.V.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public License
+ * aint with this library; see the file COPYING.LIB.  If not, write to
+ * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#pragma once
+
+#if USE(GSTREAMER)
+
+#include "GStreamerCommon.h"
+#include "GStreamerQuirks.h"
+
+namespace WebCore {
+
+class GStreamerQuirkBroadcom final : public GStreamerQuirk {
+public:
+    GStreamerQuirkBroadcom();
+    const char* identifier() final { return "Broadcom"; }
+
+    void configureElement(GstElement*, const OptionSet&) final;
+    std::optional isHardwareAccelerated(GstElementFactory*) final;
+    std::optional audioVideoDecoderFactoryListType() const final { return GST_ELEMENT_FACTORY_TYPE_PARSER; }
+    Vector disallowedWebAudioDecoders() const final { return m_disallowedWebAudioDecoders; }
+    unsigned getAdditionalPlaybinFlags() const final { return getGstPlayFlag("text") | getGstPlayFlag("native-audio"); }
+    bool shouldParseIncomingLibWebRTCBitStream() const final { return false; }
+
+private:
+    Vector m_disallowedWebAudioDecoders;
+};
+
+} // namespace WebCore
+
+#endif // USE(GSTREAMER)
diff --git a/Source/WebCore/platform/gstreamer/GStreamerQuirkRealtek.cpp b/Source/WebCore/platform/gstreamer/GStreamerQuirkRealtek.cpp
new file mode 100644
index 0000000000000..390df682013c8
--- /dev/null
+++ b/Source/WebCore/platform/gstreamer/GStreamerQuirkRealtek.cpp
@@ -0,0 +1,86 @@
+/*
+ * Copyright (C) 2024 Igalia S.L
+ * Copyright (C) 2024 Metrological Group B.V.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public License
+ * aint with this library; see the file COPYING.LIB.  If not, write to
+ * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#include "config.h"
+#include "GStreamerQuirkRealtek.h"
+
+#if USE(GSTREAMER)
+
+#include "GStreamerCommon.h"
+#include 
+
+namespace WebCore {
+
+GST_DEBUG_CATEGORY_STATIC(webkit_realtek_quirks_debug);
+#define GST_CAT_DEFAULT webkit_realtek_quirks_debug
+
+GStreamerQuirkRealtek::GStreamerQuirkRealtek()
+{
+    GST_DEBUG_CATEGORY_INIT(webkit_realtek_quirks_debug, "webkitquirksrealtek", 0, "WebKit Realtek Quirks");
+    m_disallowedWebAudioDecoders = {
+        "omxaacdec"_s,
+        "omxac3dec"_s,
+        "omxac4dec"_s,
+        "omxeac3dec"_s,
+        "omxflacdec"_s,
+        "omxlpcmdec"_s,
+        "omxmp3dec"_s,
+        "omxopusdec"_s,
+        "omxvorbisdec"_s,
+    };
+}
+
+GstElement* GStreamerQuirkRealtek::createWebAudioSink()
+{
+    auto sink = makeGStreamerElement("rtkaudiosink", nullptr);
+    RELEASE_ASSERT_WITH_MESSAGE(sink, "rtkaudiosink should be available in the system but it is not");
+    g_object_set(sink, "media-tunnel", FALSE, "audio-service", TRUE, nullptr);
+    return sink;
+}
+
+void GStreamerQuirkRealtek::configureElement(GstElement* element, const OptionSet& characteristics)
+{
+    if (!characteristics.contains(ElementRuntimeCharacteristics::IsMediaStream))
+        return;
+
+    if (gstObjectHasProperty(element, "media-tunnel")) {
+        GST_INFO("Enable 'immediate-output' in rtkaudiosink");
+        g_object_set(element, "media-tunnel", FALSE, "audio-service", TRUE, "lowdelay-sync-mode", TRUE, nullptr);
+    }
+
+    if (gstObjectHasProperty(element, "lowdelay-mode")) {
+        GST_INFO("Enable 'lowdelay-mode' in rtk omx decoder");
+        g_object_set(element, "lowdelay-mode", TRUE, nullptr);
+    }
+}
+
+std::optional GStreamerQuirkRealtek::isHardwareAccelerated(GstElementFactory* factory)
+{
+    if (g_str_has_prefix(GST_OBJECT_NAME(factory), "omx"))
+        return true;
+
+    return std::nullopt;
+}
+
+#undef GST_CAT_DEFAULT
+
+} // namespace WebCore
+
+#endif // USE(GSTREAMER)
diff --git a/Source/WebCore/platform/gstreamer/GStreamerQuirkRealtek.h b/Source/WebCore/platform/gstreamer/GStreamerQuirkRealtek.h
new file mode 100644
index 0000000000000..c46020f0ac5e4
--- /dev/null
+++ b/Source/WebCore/platform/gstreamer/GStreamerQuirkRealtek.h
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2024 Igalia S.L
+ * Copyright (C) 2024 Metrological Group B.V.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public License
+ * aint with this library; see the file COPYING.LIB.  If not, write to
+ * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#pragma once
+
+#if USE(GSTREAMER)
+
+#include "GStreamerQuirks.h"
+
+namespace WebCore {
+
+class GStreamerQuirkRealtek final : public GStreamerQuirk {
+public:
+    GStreamerQuirkRealtek();
+    const char* identifier() final { return "Realtek"; }
+
+    GstElement* createWebAudioSink() final;
+    void configureElement(GstElement*, const OptionSet&) final;
+    std::optional isHardwareAccelerated(GstElementFactory*) final;
+    Vector disallowedWebAudioDecoders() const final { return m_disallowedWebAudioDecoders; }
+    bool shouldParseIncomingLibWebRTCBitStream() const final { return false; }
+
+private:
+    Vector m_disallowedWebAudioDecoders;
+};
+
+} // namespace WebCore
+
+#endif // USE(GSTREAMER)
diff --git a/Source/WebCore/platform/gstreamer/GStreamerQuirkWesteros.cpp b/Source/WebCore/platform/gstreamer/GStreamerQuirkWesteros.cpp
new file mode 100644
index 0000000000000..0ef739ee4c562
--- /dev/null
+++ b/Source/WebCore/platform/gstreamer/GStreamerQuirkWesteros.cpp
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2024 Igalia S.L
+ * Copyright (C) 2024 Metrological Group B.V.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public License
+ * aint with this library; see the file COPYING.LIB.  If not, write to
+ * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#include "config.h"
+#include "GStreamerQuirkWesteros.h"
+
+#if USE(GSTREAMER)
+
+#include "GStreamerCommon.h"
+#include 
+
+namespace WebCore {
+
+GST_DEBUG_CATEGORY_STATIC(webkit_westeros_quirks_debug);
+#define GST_CAT_DEFAULT webkit_westeros_quirks_debug
+
+GStreamerQuirkWesteros::GStreamerQuirkWesteros()
+{
+    GST_DEBUG_CATEGORY_INIT(webkit_westeros_quirks_debug, "webkitquirkswesteros", 0, "WebKit Westeros Quirks");
+
+    auto westerosFactory = adoptGRef(gst_element_factory_find("westerossink"));
+    if (UNLIKELY(!westerosFactory))
+        return;
+
+    gst_object_unref(gst_plugin_feature_load(GST_PLUGIN_FEATURE(westerosFactory.get())));
+    for (auto* t = gst_element_factory_get_static_pad_templates(westerosFactory.get()); t; t = g_list_next(t)) {
+        auto* padtemplate = static_cast(t->data);
+        if (padtemplate->direction != GST_PAD_SINK)
+            continue;
+        if (m_sinkCaps)
+            m_sinkCaps = adoptGRef(gst_caps_merge(m_sinkCaps.leakRef(), gst_static_caps_get(&padtemplate->static_caps)));
+        else
+            m_sinkCaps = adoptGRef(gst_static_caps_get(&padtemplate->static_caps));
+    }
+}
+
+void GStreamerQuirkWesteros::configureElement(GstElement* element, const OptionSet& characteristics)
+{
+    if (!characteristics.contains(ElementRuntimeCharacteristics::IsMediaStream))
+        return;
+
+    if (!g_strcmp0(G_OBJECT_TYPE_NAME(G_OBJECT(element)), "GstWesterosSink") && gstObjectHasProperty(element, "immediate-output")) {
+        GST_INFO("Enable 'immediate-output' in WesterosSink");
+        g_object_set(element, "immediate-output", TRUE, nullptr);
+    }
+}
+
+std::optional GStreamerQuirkWesteros::isHardwareAccelerated(GstElementFactory* factory)
+{
+    if (g_str_has_prefix(GST_OBJECT_NAME(factory), "westeros"))
+        return true;
+
+    return std::nullopt;
+}
+
+#undef GST_CAT_DEFAULT
+
+} // namespace WebCore
+
+#endif // USE(GSTREAMER)
diff --git a/Source/WebCore/platform/gstreamer/GStreamerQuirkWesteros.h b/Source/WebCore/platform/gstreamer/GStreamerQuirkWesteros.h
new file mode 100644
index 0000000000000..518e512ecafec
--- /dev/null
+++ b/Source/WebCore/platform/gstreamer/GStreamerQuirkWesteros.h
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2024 Igalia S.L
+ * Copyright (C) 2024 Metrological Group B.V.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public License
+ * aint with this library; see the file COPYING.LIB.  If not, write to
+ * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#pragma once
+
+#if USE(GSTREAMER)
+
+#include "GStreamerQuirks.h"
+
+namespace WebCore {
+
+class GStreamerQuirkWesteros final : public GStreamerQuirk {
+public:
+    GStreamerQuirkWesteros();
+    const char* identifier() final { return "Westeros"; }
+
+    void configureElement(GstElement*, const OptionSet&) final;
+    std::optional isHardwareAccelerated(GstElementFactory*) final;
+
+private:
+    GRefPtr m_sinkCaps;
+};
+
+} // namespace WebCore
+
+#endif // USE(GSTREAMER)
diff --git a/Source/WebCore/platform/gstreamer/GStreamerQuirks.cpp b/Source/WebCore/platform/gstreamer/GStreamerQuirks.cpp
new file mode 100644
index 0000000000000..1b0c1c6bb750d
--- /dev/null
+++ b/Source/WebCore/platform/gstreamer/GStreamerQuirks.cpp
@@ -0,0 +1,297 @@
+/*
+ * Copyright (C) 2024 Igalia S.L
+ * Copyright (C) 2024 Metrological Group B.V.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public License
+ * aint with this library; see the file COPYING.LIB.  If not, write to
+ * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#include "config.h"
+#include "GStreamerQuirks.h"
+
+#if USE(GSTREAMER)
+
+#include "GStreamerCommon.h"
+#include "GStreamerHolePunchQuirkBcmNexus.h"
+#include "GStreamerHolePunchQuirkFake.h"
+#include "GStreamerHolePunchQuirkWesteros.h"
+#include "GStreamerQuirkAmLogic.h"
+#include "GStreamerQuirkBcmNexus.h"
+#include "GStreamerQuirkBroadcom.h"
+#include "GStreamerQuirkRealtek.h"
+#include "GStreamerQuirkWesteros.h"
+#include 
+#include 
+#include 
+
+namespace WebCore {
+
+GST_DEBUG_CATEGORY_STATIC(webkit_quirks_debug);
+#define GST_CAT_DEFAULT webkit_quirks_debug
+
+GStreamerQuirksManager& GStreamerQuirksManager::singleton()
+{
+    static NeverDestroyed sharedInstance(false, true);
+    return sharedInstance;
+}
+
+GStreamerQuirksManager::GStreamerQuirksManager(bool isForTesting, bool loadQuirksFromEnvironment)
+    : m_isForTesting(isForTesting)
+{
+    static std::once_flag debugRegisteredFlag;
+    std::call_once(debugRegisteredFlag, [] {
+        GST_DEBUG_CATEGORY_INIT(webkit_quirks_debug, "webkitquirks", 0, "WebKit Quirks");
+    });
+
+    // For the time being keep this disabled on non-WPE platforms. GTK on desktop shouldn't require
+    // quirks, for instance.
+#if !PLATFORM(WPE)
+    return;
+#endif
+
+    GST_DEBUG("Quirk manager created%s", m_isForTesting ? " for testing." : ".");
+    if (!loadQuirksFromEnvironment)
+        return;
+
+    const char* quirksListFromEnvironment = g_getenv("WEBKIT_GST_QUIRKS");
+    StringBuilder quirksListBuilder;
+    if (quirksListFromEnvironment)
+        quirksListBuilder.append(quirksListFromEnvironment);
+    else {
+#if PLATFORM(AMLOGIC)
+        quirksListBuilder.append("amlogic,");
+#endif
+#if PLATFORM(BROADCOM)
+        quirksListBuilder.append("broadcom,");
+#endif
+#if PLATFORM(BCM_NEXUS)
+        quirksListBuilder.append("bcmnexus,");
+#endif
+#if PLATFORM(REALTEK)
+        quirksListBuilder.append("realtek,");
+#endif
+#if PLATFORM(WESTEROS)
+        quirksListBuilder.append("westeros");
+#endif
+    }
+    auto quirks = quirksListBuilder.toString();
+    GST_DEBUG("Attempting to parse requested quirks: %s", quirks.ascii().data());
+    if (!quirks.isEmpty()) {
+        if (WTF::equalLettersIgnoringASCIICase(quirks, "help"_s)) {
+            WTFLogAlways("Supported quirks for WEBKIT_GST_QUIRKS are: amlogic, broadcom, bcmnexus, realtek, westeros");
+            return;
+        }
+
+        for (const auto& identifier : quirks.split(',')) {
+            std::unique_ptr quirk;
+            if (WTF::equalLettersIgnoringASCIICase(identifier, "amlogic"_s))
+                quirk = WTF::makeUnique();
+            else if (WTF::equalLettersIgnoringASCIICase(identifier, "broadcom"_s))
+                quirk = WTF::makeUnique();
+            else if (WTF::equalLettersIgnoringASCIICase(identifier, "bcmnexus"_s))
+                quirk = WTF::makeUnique();
+            else if (WTF::equalLettersIgnoringASCIICase(identifier, "realtek"_s))
+                quirk = WTF::makeUnique();
+            else if (WTF::equalLettersIgnoringASCIICase(identifier, "westeros"_s))
+                quirk = WTF::makeUnique();
+            else {
+                GST_WARNING("Unknown quirk requested: %s. Skipping", identifier.ascii().data());
+                continue;
+            }
+
+            if (!quirk->isPlatformSupported()) {
+                GST_WARNING("Quirk %s was requested but is not supported on this platform. Skipping", quirk->identifier());
+                continue;
+            }
+            m_quirks.append(WTFMove(quirk));
+        }
+    }
+
+    const char* holePunchQuirkFromEnvironment = g_getenv("WEBKIT_GST_HOLE_PUNCH_QUIRK");
+    String holePunchQuirk;
+    if (holePunchQuirkFromEnvironment)
+        holePunchQuirk = String::fromUTF8(holePunchQuirkFromEnvironment);
+    else {
+#if USE(WESTEROS_SINK)
+        holePunchQuirk = "westeros"_s;
+#elif PLATFORM(BCM_NEXUS)
+        holePunchQuirk = "bcmnexus"_s;
+#endif
+    }
+    GST_DEBUG("Attempting to parse requested hole-punch quirk: %s", holePunchQuirk.ascii().data());
+    if (holePunchQuirk.isEmpty())
+        return;
+
+    if (WTF::equalLettersIgnoringASCIICase(holePunchQuirk, "help"_s)) {
+        WTFLogAlways("Supported quirks for WEBKIT_GST_HOLE_PUNCH_QUIRK are: fake, westeros, bcmnexus");
+        return;
+    }
+
+    // TODO: Maybe check this is coherent (somehow) with the quirk(s) selected above.
+    if (WTF::equalLettersIgnoringASCIICase(holePunchQuirk, "bcmnexus"_s))
+        m_holePunchQuirk = WTF::makeUnique();
+    else if (WTF::equalLettersIgnoringASCIICase(holePunchQuirk, "westeros"_s))
+        m_holePunchQuirk = WTF::makeUnique();
+    else if (WTF::equalLettersIgnoringASCIICase(holePunchQuirk, "fake"_s))
+        m_holePunchQuirk = WTF::makeUnique();
+    else
+        GST_WARNING("HolePunch quirk %s un-supported.", holePunchQuirk.ascii().data());
+}
+
+bool GStreamerQuirksManager::isEnabled() const
+{
+    return !m_quirks.isEmpty();
+}
+
+GstElement* GStreamerQuirksManager::createWebAudioSink()
+{
+    for (const auto& quirk : m_quirks) {
+        auto* sink = quirk->createWebAudioSink();
+        if (!sink)
+            continue;
+
+        GST_DEBUG("Using WebAudioSink from quirk %s : %" GST_PTR_FORMAT, quirk->identifier(), sink);
+        return sink;
+    }
+
+    GST_DEBUG("Quirks didn't specify a WebAudioSink, falling back to default sink");
+    return createPlatformAudioSink("music"_s);
+}
+
+GstElement* GStreamerQuirksManager::createHolePunchVideoSink(bool isLegacyPlaybin, const MediaPlayer* player)
+{
+    if (!m_holePunchQuirk) {
+        GST_DEBUG("None of the quirks requested a HolePunchSink");
+        return nullptr;
+    }
+    auto sink = m_holePunchQuirk->createHolePunchVideoSink(isLegacyPlaybin, player);
+    GST_DEBUG("Using HolePunchSink from quirk %s : %" GST_PTR_FORMAT, m_holePunchQuirk->identifier(), sink);
+    return sink;
+}
+
+void GStreamerQuirksManager::setHolePunchVideoRectangle(GstElement* videoSink, const IntRect& rect)
+{
+    if (!m_holePunchQuirk) {
+        GST_DEBUG("None of the quirks requested a HolePunchSink");
+        return;
+    }
+
+    if (!m_holePunchQuirk->setHolePunchVideoRectangle(videoSink, rect))
+        GST_WARNING("Hole punch video rectangle configuration failed.");
+}
+
+bool GStreamerQuirksManager::sinksRequireClockSynchronization() const
+{
+    if (!m_holePunchQuirk)
+        return true;
+
+    return m_holePunchQuirk->requiresClockSynchronization();
+}
+
+void GStreamerQuirksManager::configureElement(GstElement* element, OptionSet&& characteristics)
+{
+    GST_DEBUG("Configuring element %" GST_PTR_FORMAT, element);
+    for (const auto& quirk : m_quirks)
+        quirk->configureElement(element, characteristics);
+}
+
+std::optional GStreamerQuirksManager::isHardwareAccelerated(GstElementFactory* factory) const
+{
+    for (const auto& quirk : m_quirks) {
+        auto result = quirk->isHardwareAccelerated(factory);
+        if (!result)
+            continue;
+
+        GST_DEBUG("Setting %" GST_PTR_FORMAT " as %s accelerated from quirk %s", factory, quirk->identifier(), *result ? "hardware" : "software");
+        return *result;
+    }
+
+    return std::nullopt;
+}
+
+bool GStreamerQuirksManager::supportsVideoHolePunchRendering() const
+{
+    return m_holePunchQuirk.get();
+}
+
+GstElementFactoryListType GStreamerQuirksManager::audioVideoDecoderFactoryListType() const
+{
+    for (const auto& quirk : m_quirks) {
+        auto result = quirk->audioVideoDecoderFactoryListType();
+        if (!result)
+            continue;
+
+        GST_DEBUG("Quirk %s requests audio/video decoder factory list override to %" G_GUINT32_FORMAT, quirk->identifier(), static_cast(*result));
+        return *result;
+    }
+
+    return GST_ELEMENT_FACTORY_TYPE_DECODER;
+}
+
+Vector GStreamerQuirksManager::disallowedWebAudioDecoders() const
+{
+    Vector result;
+    for (const auto& quirk : m_quirks)
+        result.appendVector(quirk->disallowedWebAudioDecoders());
+
+    return result;
+}
+
+void GStreamerQuirksManager::setHolePunchEnabledForTesting(bool enabled)
+{
+    if (enabled)
+        m_holePunchQuirk = WTF::makeUnique();
+    else
+        m_holePunchQuirk = nullptr;
+}
+
+unsigned GStreamerQuirksManager::getAdditionalPlaybinFlags() const
+{
+    unsigned flags = 0;
+#if USE(GSTREAMER_NATIVE_VIDEO)
+    flags |= getGstPlayFlag("native-video");
+#else
+    flags |= getGstPlayFlag("soft-colorbalance");
+#endif
+#if USE(GSTREAMER_NATIVE_AUDIO)
+    flags |= getGstPlayFlag("native-audio");
+#endif
+#if USE(GSTREAMER_TEXT_SINK)
+    flags |= getGstPlayFlag("text");
+#endif
+    for (const auto& quirk : m_quirks) {
+        if (auto additionalFlags = quirk->getAdditionalPlaybinFlags()) {
+            GST_DEBUG("Quirk %s requests these playbin flags: %u", quirk->identifier(), additionalFlags);
+            flags |= additionalFlags;
+        }
+    }
+
+    return flags;
+}
+
+bool GStreamerQuirksManager::shouldParseIncomingLibWebRTCBitStream() const
+{
+    for (auto& quirk : m_quirks) {
+        if (!quirk->shouldParseIncomingLibWebRTCBitStream())
+            return false;
+    }
+    return true;
+}
+
+#undef GST_CAT_DEFAULT
+
+} // namespace WebCore
+
+#endif // USE(GSTREAMER)
diff --git a/Source/WebCore/platform/gstreamer/GStreamerQuirks.h b/Source/WebCore/platform/gstreamer/GStreamerQuirks.h
new file mode 100644
index 0000000000000..273ce14f6cfad
--- /dev/null
+++ b/Source/WebCore/platform/gstreamer/GStreamerQuirks.h
@@ -0,0 +1,118 @@
+/*
+ * Copyright (C) 2024 Igalia S.L
+ * Copyright (C) 2024 Metrological Group B.V.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public License
+ * aint with this library; see the file COPYING.LIB.  If not, write to
+ * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#pragma once
+
+#if USE(GSTREAMER)
+
+#include "GStreamerCommon.h"
+#include "MediaPlayer.h"
+#include 
+#include 
+#include 
+
+namespace WebCore {
+
+enum class ElementRuntimeCharacteristics : uint8_t {
+    IsMediaStream = 1 << 0,
+    HasVideo = 1 << 1,
+    HasAudio = 1 << 2,
+    IsLiveStream = 1 << 3,
+};
+
+class GStreamerQuirkBase {
+    WTF_MAKE_FAST_ALLOCATED;
+
+public:
+    GStreamerQuirkBase() = default;
+    virtual ~GStreamerQuirkBase() = default;
+
+    virtual const char* identifier() = 0;
+};
+
+class GStreamerQuirk : public GStreamerQuirkBase {
+    WTF_MAKE_FAST_ALLOCATED;
+public:
+    GStreamerQuirk() = default;
+    virtual ~GStreamerQuirk() = default;
+
+    virtual bool isPlatformSupported() const { return true; }
+    virtual GstElement* createWebAudioSink() { return nullptr; }
+    virtual void configureElement(GstElement*, const OptionSet&) { }
+    virtual std::optional isHardwareAccelerated(GstElementFactory*) { return std::nullopt; }
+    virtual std::optional audioVideoDecoderFactoryListType() const { return std::nullopt; }
+    virtual Vector disallowedWebAudioDecoders() const { return { }; }
+    virtual unsigned getAdditionalPlaybinFlags() const { return getGstPlayFlag("text") | getGstPlayFlag("soft-colorbalance"); }
+    virtual bool shouldParseIncomingLibWebRTCBitStream() const { return true; }
+};
+
+class GStreamerHolePunchQuirk : public GStreamerQuirkBase {
+    WTF_MAKE_FAST_ALLOCATED;
+public:
+    GStreamerHolePunchQuirk() = default;
+    virtual ~GStreamerHolePunchQuirk() = default;
+
+    virtual GstElement* createHolePunchVideoSink(bool, const MediaPlayer*) { return nullptr; }
+    virtual bool setHolePunchVideoRectangle(GstElement*, const IntRect&) { return false; }
+    virtual bool requiresClockSynchronization() const { return true; }
+};
+
+class GStreamerQuirksManager : public RefCounted {
+    friend NeverDestroyed;
+    WTF_MAKE_FAST_ALLOCATED;
+
+public:
+    static GStreamerQuirksManager& singleton();
+
+    static RefPtr createForTesting()
+    {
+        return adoptRef(*new GStreamerQuirksManager(true, false));
+    }
+
+    bool isEnabled() const;
+
+    GstElement* createWebAudioSink();
+    void configureElement(GstElement*, OptionSet&&);
+    std::optional isHardwareAccelerated(GstElementFactory*) const;
+    GstElementFactoryListType audioVideoDecoderFactoryListType() const;
+    Vector disallowedWebAudioDecoders() const;
+
+    bool supportsVideoHolePunchRendering() const;
+    GstElement* createHolePunchVideoSink(bool isLegacyPlaybin, const MediaPlayer*);
+    void setHolePunchVideoRectangle(GstElement*, const IntRect&);
+    bool sinksRequireClockSynchronization() const;
+
+    void setHolePunchEnabledForTesting(bool);
+
+    unsigned getAdditionalPlaybinFlags() const;
+
+    bool shouldParseIncomingLibWebRTCBitStream() const;
+
+private:
+    GStreamerQuirksManager(bool, bool);
+
+    Vector> m_quirks;
+    std::unique_ptr m_holePunchQuirk;
+    bool m_isForTesting { false };
+};
+
+} // namespace WebCore
+
+#endif // USE(GSTREAMER)
diff --git a/Source/WebCore/platform/mediastream/libwebrtc/gstreamer/GStreamerVideoDecoderFactory.cpp b/Source/WebCore/platform/mediastream/libwebrtc/gstreamer/GStreamerVideoDecoderFactory.cpp
index e892a0fd89ce7..17d9a3cf3c916 100644
--- a/Source/WebCore/platform/mediastream/libwebrtc/gstreamer/GStreamerVideoDecoderFactory.cpp
+++ b/Source/WebCore/platform/mediastream/libwebrtc/gstreamer/GStreamerVideoDecoderFactory.cpp
@@ -23,6 +23,7 @@
 #if ENABLE(VIDEO) && ENABLE(MEDIA_STREAM) && USE(LIBWEBRTC) && USE(GSTREAMER)
 #include "GStreamerVideoDecoderFactory.h"
 
+#include "GStreamerQuirks.h"
 #include "GStreamerVideoCommon.h"
 #include "GStreamerRegistryScanner.h"
 #include "GStreamerVideoFrameLibWebRTC.h"
@@ -112,6 +113,16 @@ class GStreamerWebRTCVideoDecoder : public webrtc::VideoDecoder {
         m_needsKeyframe = true;
     }
 
+    static unsigned getGstAutoplugSelectResult(const char* nick)
+    {
+        static GEnumClass* enumClass = static_cast(g_type_class_ref(g_type_from_name("GstAutoplugSelectResult")));
+        ASSERT(enumClass);
+        GEnumValue* ev = g_enum_get_value_by_nick(enumClass, nick);
+        if (!ev)
+            return 0;
+        return ev->value;
+    }
+
     bool Configure(const webrtc::VideoDecoder::Settings& codecSettings) override
     {
         m_src = makeElement("appsrc");
@@ -128,9 +139,17 @@ class GStreamerWebRTCVideoDecoder : public webrtc::VideoDecoder {
 
         auto sinkpad = adoptGRef(gst_element_get_static_pad(capsfilter, "sink"));
         g_signal_connect(decoder, "pad-added", G_CALLBACK(decodebinPadAddedCb), sinkpad.get());
-#if PLATFORM(BROADCOM) || PLATFORM(REALTEK)
-        g_signal_connect(decoder, "autoplug-select", G_CALLBACK(decodebinAutoplugSelect), nullptr);
-#endif
+
+        auto& quirksManager = GStreamerQuirksManager::singleton();
+        if (quirksManager.isEnabled()) {
+            g_signal_connect(decoder, "autoplug-select", G_CALLBACK(+[](GstElement*, GstPad*, GstCaps*, GstElementFactory* factory, gpointer) -> unsigned {
+                auto& quirksManager = GStreamerQuirksManager::singleton();
+                auto isHardwareAccelerated = quirksManager.isHardwareAccelerated(factory).value_or(false);
+                if (isHardwareAccelerated)
+                    return getGstAutoplugSelectResult("skip");
+                return getGstAutoplugSelectResult("try");
+            }), nullptr);
+        }
 
         // Make the decoder output "parsed" frames only and let the main decodebin
         // do the real decoding. This allows us to have optimized decoding/rendering
@@ -350,10 +369,13 @@ class GStreamerWebRTCVideoDecoder : public webrtc::VideoDecoder {
 
 class H264Decoder : public GStreamerWebRTCVideoDecoder {
 public:
-    H264Decoder() {
-#if !PLATFORM(REALTEK) && !PLATFORM(BROADCOM)
+    H264Decoder()
+    {
         m_requireParse = true;
-#endif
+
+        auto& quirksManager = GStreamerQuirksManager::singleton();
+        if (quirksManager.isEnabled())
+            m_requireParse = quirksManager.shouldParseIncomingLibWebRTCBitStream();
     }
 
     bool Configure(const webrtc::VideoDecoder::Settings& codecSettings) final
diff --git a/Source/WebCore/testing/Internals.cpp b/Source/WebCore/testing/Internals.cpp
index cbeb123c32e89..cad9428421394 100644
--- a/Source/WebCore/testing/Internals.cpp
+++ b/Source/WebCore/testing/Internals.cpp
@@ -4116,6 +4116,15 @@ ExceptionOr Internals::setOverridePreferredDynamicRangeMode(HTMLMediaEleme
     return { };
 }
 
+void Internals::enableGStreamerHolePunching(HTMLVideoElement& element)
+{
+#if USE(GSTREAMER)
+    element.enableGStreamerHolePunching();
+#else
+    UNUSED_PARAM(element);
+#endif
+}
+
 #endif
 
 bool Internals::isSelectPopupVisible(HTMLSelectElement& element)
diff --git a/Source/WebCore/testing/Internals.h b/Source/WebCore/testing/Internals.h
index 8b6f9c21e35c5..e702cd4f2c34a 100644
--- a/Source/WebCore/testing/Internals.h
+++ b/Source/WebCore/testing/Internals.h
@@ -705,6 +705,8 @@ class Internals final : public RefCounted, private ContextDestruction
     double elementEffectivePlaybackRate(const HTMLMediaElement&);
 
     ExceptionOr setOverridePreferredDynamicRangeMode(HTMLMediaElement&, const String&);
+
+    void enableGStreamerHolePunching(HTMLVideoElement&);
 #endif
 
     ExceptionOr setIsPlayingToBluetoothOverride(std::optional);
diff --git a/Source/WebCore/testing/Internals.idl b/Source/WebCore/testing/Internals.idl
index a3684eaeb36ed..82a8e99d7bc37 100644
--- a/Source/WebCore/testing/Internals.idl
+++ b/Source/WebCore/testing/Internals.idl
@@ -776,6 +776,8 @@ typedef (FetchRequest or FetchResponse) FetchObject;
     [Conditional=VIDEO] undefined setOverridePreferredDynamicRangeMode(HTMLMediaElement media, DOMString mode);
     [Conditional=VIDEO] double elementEffectivePlaybackRate(HTMLMediaElement media);
 
+    [Conditional=VIDEO] undefined enableGStreamerHolePunching(HTMLVideoElement element);
+
     undefined setIsPlayingToBluetoothOverride(optional boolean? isPlaying = null);
 
     [Conditional=LEGACY_ENCRYPTED_MEDIA] undefined initializeMockCDM();