From 37bc7427407685a224044ddc3df4b81c41d6fd38 Mon Sep 17 00:00:00 2001
From: Philippe Normand <philn@igalia.com>
Date: Mon, 28 Aug 2023 01:34:28 -0700
Subject: [PATCH] [GStreamer] Prevent a crash when fetching data on stopped
 MediaRecorder https://bugs.webkit.org/show_bug.cgi?id=260649
 rdar://problem/114370120

Reviewed by Xabier Rodriguez-Calvar.

The backend (GStreamer transcoder) is now clearly separated from the MediaRecorderPrivate, so that
fetchData() can create a weak pointer to be used from the main thread. If the backend was destroyed
in-flight no unsafe memory access is performed.

Test: http/wpt/mediarecorder/MediaRecorder-start-stop-crash.html
Canonical link: https://commits.webkit.org/267345@main
---
 .../graphics/gstreamer/GRefPtrGStreamer.cpp   |  52 +++++++
 .../graphics/gstreamer/GRefPtrGStreamer.h     |  15 ++
 .../MediaRecorderPrivateGStreamer.cpp         | 140 ++++++++++++------
 .../MediaRecorderPrivateGStreamer.h           |  57 +++++--
 4 files changed, 206 insertions(+), 62 deletions(-)

diff --git a/Source/WebCore/platform/graphics/gstreamer/GRefPtrGStreamer.cpp b/Source/WebCore/platform/graphics/gstreamer/GRefPtrGStreamer.cpp
index cc0afa79..822f5aaa 100644
--- a/Source/WebCore/platform/graphics/gstreamer/GRefPtrGStreamer.cpp
+++ b/Source/WebCore/platform/graphics/gstreamer/GRefPtrGStreamer.cpp
@@ -34,6 +34,10 @@
 #undef GST_USE_UNSTABLE_API
 #endif
 
+#if USE(GSTREAMER_TRANSCODER)
+#include <gst/transcoder/gsttranscoder.h>
+#endif
+
 namespace WTF {
 
 template<> GRefPtr<GstMiniObject> adoptGRef(GstMiniObject* ptr)
@@ -754,6 +758,54 @@ template<> void derefGPtr<GstRTPHeaderExtension>(GstRTPHeaderExtension* ptr)
 
 #endif // USE(GSTREAMER_WEBRTC)
 
+#if USE(GSTREAMER_TRANSCODER)
+
+template<>
+GRefPtr<GstTranscoder> adoptGRef(GstTranscoder* ptr)
+{
+    return GRefPtr<GstTranscoder>(ptr, GRefPtrAdopt);
+}
+
+template<>
+GstTranscoder* refGPtr<GstTranscoder>(GstTranscoder* ptr)
+{
+    if (ptr)
+        gst_object_ref(GST_OBJECT_CAST(ptr));
+
+    return ptr;
+}
+
+template<>
+void derefGPtr<GstTranscoder>(GstTranscoder* ptr)
+{
+    if (ptr)
+        gst_object_unref(ptr);
+}
+
+template<>
+GRefPtr<GstTranscoderSignalAdapter> adoptGRef(GstTranscoderSignalAdapter* ptr)
+{
+    return GRefPtr<GstTranscoderSignalAdapter>(ptr, GRefPtrAdopt);
+}
+
+template<>
+GstTranscoderSignalAdapter* refGPtr<GstTranscoderSignalAdapter>(GstTranscoderSignalAdapter* ptr)
+{
+    if (ptr)
+        g_object_ref(G_OBJECT(ptr));
+
+    return ptr;
+}
+
+template<>
+void derefGPtr<GstTranscoderSignalAdapter>(GstTranscoderSignalAdapter* ptr)
+{
+    if (ptr)
+        g_object_unref(ptr);
+}
+
+#endif // USE(GSTREAMER_TRANSCODER)
+
 } // namespace WTF
 
 #endif // USE(GSTREAMER)
diff --git a/Source/WebCore/platform/graphics/gstreamer/GRefPtrGStreamer.h b/Source/WebCore/platform/graphics/gstreamer/GRefPtrGStreamer.h
index 57c93254..5ddbeec1 100644
--- a/Source/WebCore/platform/graphics/gstreamer/GRefPtrGStreamer.h
+++ b/Source/WebCore/platform/graphics/gstreamer/GRefPtrGStreamer.h
@@ -45,6 +45,11 @@ typedef struct _GstWebRTCRTPTransceiver GstWebRTCRTPTransceiver;
 typedef struct _GstRTPHeaderExtension GstRTPHeaderExtension;
 #endif
 
+#if USE(GSTREAMER_TRANSCODER)
+typedef struct _GstTranscoder GstTranscoder;
+typedef struct _GstTranscoderSignalAdapter GstTranscoderSignalAdapter;
+#endif
+
 namespace WTF {
 
 template<> GRefPtr<GstPlugin> adoptGRef(GstPlugin* ptr);
@@ -197,6 +202,16 @@ template<> void derefGPtr<GstRTPHeaderExtension>(GstRTPHeaderExtension*);
 
 #endif
 
+#if USE(GSTREAMER_TRANSCODER)
+template<> GRefPtr<GstTranscoder> adoptGRef(GstTranscoder*);
+template<> GstTranscoder* refGPtr<GstTranscoder>(GstTranscoder*);
+template<> void derefGPtr<GstTranscoder>(GstTranscoder*);
+
+template<> GRefPtr<GstTranscoderSignalAdapter> adoptGRef(GstTranscoderSignalAdapter*);
+template<> GstTranscoderSignalAdapter* refGPtr<GstTranscoderSignalAdapter>(GstTranscoderSignalAdapter*);
+template<> void derefGPtr<GstTranscoderSignalAdapter>(GstTranscoderSignalAdapter*);
+#endif // USE(GSTREAMER_TRANSCODER)
+
 } // namespace WTF
 
 #endif // USE(GSTREAMER)
diff --git a/Source/WebCore/platform/mediarecorder/MediaRecorderPrivateGStreamer.cpp b/Source/WebCore/platform/mediarecorder/MediaRecorderPrivateGStreamer.cpp
index 835e357b..968eee75 100644
--- a/Source/WebCore/platform/mediarecorder/MediaRecorderPrivateGStreamer.cpp
+++ b/Source/WebCore/platform/mediarecorder/MediaRecorderPrivateGStreamer.cpp
@@ -30,6 +30,7 @@
 #include "MediaRecorderPrivateOptions.h"
 #include "MediaStreamPrivate.h"
 #include <gst/app/gstappsink.h>
+#include <gst/transcoder/gsttranscoder.h>
 #include <wtf/Scope.h>
 
 namespace WebCore {
@@ -46,21 +47,73 @@ std::unique_ptr<MediaRecorderPrivateGStreamer> MediaRecorderPrivateGStreamer::cr
         GST_DEBUG_CATEGORY_INIT(webkit_media_recorder_debug, "webkitmediarecorder", 0, "WebKit MediaStream recorder");
     });
 
-    auto recorder = makeUnique<MediaRecorderPrivateGStreamer>(stream, options);
+    auto recorder = MediaRecorderPrivateBackend::create(stream, options);
     if (!recorder->preparePipeline())
         return nullptr;
 
-    return recorder;
+    return makeUnique<MediaRecorderPrivateGStreamer>(recorder.releaseNonNull());
 }
 
-MediaRecorderPrivateGStreamer::MediaRecorderPrivateGStreamer(MediaStreamPrivate& stream, const MediaRecorderPrivateOptions& options)
+MediaRecorderPrivateGStreamer::MediaRecorderPrivateGStreamer(Ref<MediaRecorderPrivateBackend>&& recorder)
+    : m_recorder(WTFMove(recorder))
+{
+    m_recorder->setSelectTracksCallback([this](auto selectedTracks) {
+        if (selectedTracks.audioTrack) {
+            setAudioSource(&selectedTracks.audioTrack->source());
+            checkTrackState(*selectedTracks.audioTrack);
+        }
+        if (selectedTracks.videoTrack) {
+            setVideoSource(&selectedTracks.videoTrack->source());
+            checkTrackState(*selectedTracks.videoTrack);
+        }
+    });
+}
+
+void MediaRecorderPrivateGStreamer::startRecording(StartRecordingCallback&& callback)
+{
+    m_recorder->startRecording(WTFMove(callback));
+}
+
+void MediaRecorderPrivateGStreamer::stopRecording(CompletionHandler<void()>&& completionHandler)
+{
+    m_recorder->stopRecording(WTFMove(completionHandler));
+}
+
+void MediaRecorderPrivateGStreamer::fetchData(FetchDataCallback&& completionHandler)
+{
+    m_recorder->fetchData(WTFMove(completionHandler));
+}
+
+void MediaRecorderPrivateGStreamer::pauseRecording(CompletionHandler<void()>&& completionHandler)
+{
+    m_recorder->pauseRecording(WTFMove(completionHandler));
+}
+
+void MediaRecorderPrivateGStreamer::resumeRecording(CompletionHandler<void()>&& completionHandler)
+{
+    m_recorder->resumeRecording(WTFMove(completionHandler));
+}
+
+const String& MediaRecorderPrivateGStreamer::mimeType() const
+{
+    return m_recorder->mimeType();
+}
+
+bool MediaRecorderPrivateGStreamer::isTypeSupported(const ContentType& contentType)
+{
+    auto& scanner = GStreamerRegistryScanner::singleton();
+    return scanner.isContentTypeSupported(GStreamerRegistryScanner::Configuration::Encoding, contentType, { }) > MediaPlayerEnums::SupportsType::IsNotSupported;
+}
+
+MediaRecorderPrivateBackend::MediaRecorderPrivateBackend(MediaStreamPrivate& stream, const MediaRecorderPrivateOptions& options)
     : m_stream(stream)
     , m_options(options)
 {
 }
 
-MediaRecorderPrivateGStreamer::~MediaRecorderPrivateGStreamer()
+MediaRecorderPrivateBackend::~MediaRecorderPrivateBackend()
 {
+    m_selectTracksCallback.reset();
     if (m_src)
         webkitMediaStreamSrcSignalEndOfStream(WEBKIT_MEDIA_STREAM_SRC(m_src.get()));
     if (m_transcoder) {
@@ -69,7 +122,7 @@ MediaRecorderPrivateGStreamer::~MediaRecorderPrivateGStreamer()
     }
 }
 
-void MediaRecorderPrivateGStreamer::startRecording(StartRecordingCallback&& callback)
+void MediaRecorderPrivateBackend::startRecording(MediaRecorderPrivate::StartRecordingCallback&& callback)
 {
     if (!m_pipeline)
         preparePipeline();
@@ -78,7 +131,7 @@ void MediaRecorderPrivateGStreamer::startRecording(StartRecordingCallback&& call
     gst_transcoder_run_async(m_transcoder.get());
 }
 
-void MediaRecorderPrivateGStreamer::stopRecording(CompletionHandler<void()>&& completionHandler)
+void MediaRecorderPrivateBackend::stopRecording(CompletionHandler<void()>&& completionHandler)
 {
     GST_DEBUG_OBJECT(m_transcoder.get(), "Stop requested, pushing EOS event");
 
@@ -99,24 +152,31 @@ void MediaRecorderPrivateGStreamer::stopRecording(CompletionHandler<void()>&& co
     bool isEOS = false;
     while (!isEOS) {
         LockHolder lock(m_eosLock);
-        m_eosCondition.waitFor(m_eosLock, 200_ms, [weakThis = WeakPtr { this }]() -> bool {
-            if (!weakThis)
-                return true;
-            return weakThis->m_eos;
+        m_eosCondition.waitFor(m_eosLock, 200_ms, [weakThis = ThreadSafeWeakPtr { *this }]() -> bool {
+            if (auto strongThis = weakThis.get())
+                return strongThis->m_eos;
+            return true;
         });
         isEOS = m_eos;
     }
 }
 
-void MediaRecorderPrivateGStreamer::fetchData(FetchDataCallback&& completionHandler)
+void MediaRecorderPrivateBackend::fetchData(MediaRecorderPrivate::FetchDataCallback&& completionHandler)
 {
-    Locker locker { m_dataLock };
-    GST_DEBUG_OBJECT(m_transcoder.get(), "Transfering %zu encoded bytes", m_data.size());
-    auto buffer = m_data.take();
-    completionHandler(WTFMove(buffer), mimeType(), m_position);
+    callOnMainThread([this, weakThis = ThreadSafeWeakPtr { *this }, completionHandler = WTFMove(completionHandler)]() mutable {
+        auto strongThis = weakThis.get();
+        if (!strongThis) {
+            completionHandler(nullptr, mimeType(), 0);
+            return;
+        }
+        Locker locker { m_dataLock };
+        GST_DEBUG_OBJECT(m_transcoder.get(), "Transfering %zu encoded bytes", m_data.size());
+        auto buffer = m_data.take();
+        completionHandler(WTFMove(buffer), mimeType(), m_position);
+    });
 }
 
-void MediaRecorderPrivateGStreamer::pauseRecording(CompletionHandler<void()>&& completionHandler)
+void MediaRecorderPrivateBackend::pauseRecording(CompletionHandler<void()>&& completionHandler)
 {
     GST_INFO_OBJECT(m_transcoder.get(), "Pausing");
     if (m_pipeline)
@@ -130,7 +190,7 @@ void MediaRecorderPrivateGStreamer::pauseRecording(CompletionHandler<void()>&& c
     completionHandler();
 }
 
-void MediaRecorderPrivateGStreamer::resumeRecording(CompletionHandler<void()>&& completionHandler)
+void MediaRecorderPrivateBackend::resumeRecording(CompletionHandler<void()>&& completionHandler)
 {
     GST_INFO_OBJECT(m_transcoder.get(), "Resuming");
     auto selectedTracks = MediaRecorderPrivate::selectTracks(stream());
@@ -143,7 +203,7 @@ void MediaRecorderPrivateGStreamer::resumeRecording(CompletionHandler<void()>&&
     completionHandler();
 }
 
-const String& MediaRecorderPrivateGStreamer::mimeType() const
+const String& MediaRecorderPrivateBackend::mimeType() const
 {
     static NeverDestroyed<const String> MP4AUDIOMIMETYPE(MAKE_STATIC_STRING_IMPL("audio/mp4"));
     static NeverDestroyed<const String> MP4VIDEOMIMETYPE(MAKE_STATIC_STRING_IMPL("video/mp4"));
@@ -152,13 +212,7 @@ const String& MediaRecorderPrivateGStreamer::mimeType() const
     return selectedTracks.videoTrack ? MP4VIDEOMIMETYPE : MP4AUDIOMIMETYPE;
 }
 
-bool MediaRecorderPrivateGStreamer::isTypeSupported(const ContentType& contentType)
-{
-    auto& scanner = GStreamerRegistryScanner::singleton();
-    return scanner.isContentTypeSupported(GStreamerRegistryScanner::Configuration::Encoding, contentType, { }) > MediaPlayerEnums::SupportsType::IsNotSupported;
-}
-
-GRefPtr<GstEncodingContainerProfile> MediaRecorderPrivateGStreamer::containerProfile()
+GRefPtr<GstEncodingContainerProfile> MediaRecorderPrivateBackend::containerProfile()
 {
     auto selectedTracks = MediaRecorderPrivate::selectTracks(m_stream);
     auto mimeType = this->mimeType();
@@ -239,38 +293,36 @@ GRefPtr<GstEncodingContainerProfile> MediaRecorderPrivateGStreamer::containerPro
     return profile;
 }
 
-void MediaRecorderPrivateGStreamer::setSource(GstElement* element)
+void MediaRecorderPrivateBackend::setSource(GstElement* element)
 {
     auto selectedTracks = MediaRecorderPrivate::selectTracks(stream());
     bool onlyTrack = (selectedTracks.audioTrack && !selectedTracks.videoTrack) || (selectedTracks.videoTrack && !selectedTracks.audioTrack);
     auto* src = WEBKIT_MEDIA_STREAM_SRC(element);
-    if (selectedTracks.audioTrack) {
+    if (selectedTracks.audioTrack)
         webkitMediaStreamSrcAddTrack(src, selectedTracks.audioTrack, onlyTrack);
-        setAudioSource(&selectedTracks.audioTrack->source());
-        checkTrackState(*selectedTracks.audioTrack);
-    }
-    if (selectedTracks.videoTrack) {
+    if (selectedTracks.videoTrack)
         webkitMediaStreamSrcAddTrack(src, selectedTracks.videoTrack, onlyTrack);
-        setVideoSource(&selectedTracks.videoTrack->source());
-        checkTrackState(*selectedTracks.videoTrack);
+    if (m_selectTracksCallback) {
+        auto& callback = *m_selectTracksCallback;
+        callback(selectedTracks);
     }
     m_src = element;
 }
 
-void MediaRecorderPrivateGStreamer::setSink(GstElement* element)
+void MediaRecorderPrivateBackend::setSink(GstElement* element)
 {
     static GstAppSinkCallbacks callbacks = {
         nullptr,
         [](GstAppSink* sink, gpointer userData) -> GstFlowReturn {
             auto sample = adoptGRef(gst_app_sink_pull_preroll(sink));
             if (sample)
-                static_cast<MediaRecorderPrivateGStreamer*>(userData)->processSample(WTFMove(sample));
+                static_cast<MediaRecorderPrivateBackend*>(userData)->processSample(WTFMove(sample));
             return gst_app_sink_is_eos(sink) ? GST_FLOW_EOS : GST_FLOW_OK;
         },
         [](GstAppSink* sink, gpointer userData) -> GstFlowReturn {
             auto sample = adoptGRef(gst_app_sink_pull_sample(sink));
             if (sample)
-                static_cast<MediaRecorderPrivateGStreamer*>(userData)->processSample(WTFMove(sample));
+                static_cast<MediaRecorderPrivateBackend*>(userData)->processSample(WTFMove(sample));
             return gst_app_sink_is_eos(sink) ? GST_FLOW_EOS : GST_FLOW_OK;
         },
         // new_event
@@ -282,7 +334,7 @@ void MediaRecorderPrivateGStreamer::setSink(GstElement* element)
     m_sink = element;
 }
 
-void MediaRecorderPrivateGStreamer::configureVideoEncoder(GstElement* element)
+void MediaRecorderPrivateBackend::configureVideoEncoder(GstElement* element)
 {
     auto format = adoptGRef(gst_encoding_profile_get_format(GST_ENCODING_PROFILE(m_videoEncodingProfile.get())));
     g_object_set(element, "format", format.get(), nullptr);
@@ -299,7 +351,7 @@ void MediaRecorderPrivateGStreamer::configureVideoEncoder(GstElement* element)
         g_object_set(element, "bitrate", bitrate / 1024, nullptr);
 }
 
-bool MediaRecorderPrivateGStreamer::preparePipeline()
+bool MediaRecorderPrivateBackend::preparePipeline()
 {
     auto profile = containerProfile();
     if (!profile)
@@ -309,11 +361,11 @@ bool MediaRecorderPrivateGStreamer::preparePipeline()
     gst_transcoder_set_avoid_reencoding(m_transcoder.get(), true);
     m_pipeline = gst_transcoder_get_pipeline(m_transcoder.get());
 
-    g_signal_connect_swapped(m_pipeline.get(), "source-setup", G_CALLBACK(+[](MediaRecorderPrivateGStreamer* recorder, GstElement* sourceElement) {
+    g_signal_connect_swapped(m_pipeline.get(), "source-setup", G_CALLBACK(+[](MediaRecorderPrivateBackend* recorder, GstElement* sourceElement) {
         recorder->setSource(sourceElement);
     }), this);
 
-    g_signal_connect_swapped(m_pipeline.get(), "element-setup", G_CALLBACK(+[](MediaRecorderPrivateGStreamer* recorder, GstElement* element) {
+    g_signal_connect_swapped(m_pipeline.get(), "element-setup", G_CALLBACK(+[](MediaRecorderPrivateBackend* recorder, GstElement* element) {
         if (WEBKIT_IS_WEBRTC_VIDEO_ENCODER(element)) {
             recorder->configureVideoEncoder(element);
             return;
@@ -330,18 +382,18 @@ bool MediaRecorderPrivateGStreamer::preparePipeline()
         GST_WARNING("%s details: %" GST_PTR_FORMAT, error->message, details);
     }), nullptr);
 
-    g_signal_connect_swapped(m_signalAdapter.get(), "done", G_CALLBACK(+[](MediaRecorderPrivateGStreamer* recorder) {
+    g_signal_connect_swapped(m_signalAdapter.get(), "done", G_CALLBACK(+[](MediaRecorderPrivateBackend* recorder) {
         recorder->notifyEOS();
     }), this);
 
-    g_signal_connect_swapped(m_signalAdapter.get(), "position-updated", G_CALLBACK(+[](MediaRecorderPrivateGStreamer* recorder, GstClockTime position) {
+    g_signal_connect_swapped(m_signalAdapter.get(), "position-updated", G_CALLBACK(+[](MediaRecorderPrivateBackend* recorder, GstClockTime position) {
         recorder->notifyPosition(position);
     }), this);
 
     return true;
 }
 
-void MediaRecorderPrivateGStreamer::processSample(GRefPtr<GstSample>&& sample)
+void MediaRecorderPrivateBackend::processSample(GRefPtr<GstSample>&& sample)
 {
     auto* sampleBuffer = gst_sample_get_buffer(sample.get());
     GstMappedBuffer buffer(sampleBuffer, GST_MAP_READ);
@@ -351,7 +403,7 @@ void MediaRecorderPrivateGStreamer::processSample(GRefPtr<GstSample>&& sample)
     m_data.append(buffer.data(), buffer.size());
 }
 
-void MediaRecorderPrivateGStreamer::notifyEOS()
+void MediaRecorderPrivateBackend::notifyEOS()
 {
     GST_DEBUG("EOS received");
     LockHolder lock(m_eosLock);
diff --git a/Source/WebCore/platform/mediarecorder/MediaRecorderPrivateGStreamer.h b/Source/WebCore/platform/mediarecorder/MediaRecorderPrivateGStreamer.h
index b15c7be4..d8379055 100644
--- a/Source/WebCore/platform/mediarecorder/MediaRecorderPrivateGStreamer.h
+++ b/Source/WebCore/platform/mediarecorder/MediaRecorderPrivateGStreamer.h
@@ -24,7 +24,6 @@
 #include "GRefPtrGStreamer.h"
 #include "MediaRecorderPrivate.h"
 #include "SharedBuffer.h"
-#include <gst/transcoder/gsttranscoder.h>
 #include <wtf/CompletionHandler.h>
 #include <wtf/Condition.h>
 #include <wtf/Forward.h>
@@ -37,29 +36,31 @@ class ContentType;
 class MediaStreamTrackPrivate;
 struct MediaRecorderPrivateOptions;
 
-class MediaRecorderPrivateGStreamer final : public MediaRecorderPrivate, public CanMakeWeakPtr<MediaRecorderPrivateGStreamer> {
+class MediaRecorderPrivateBackend : public ThreadSafeRefCountedAndCanMakeThreadSafeWeakPtr<MediaRecorderPrivateBackend, WTF::DestructionThread::Main> {
     WTF_MAKE_FAST_ALLOCATED;
 
 public:
-    static std::unique_ptr<MediaRecorderPrivateGStreamer> create(MediaStreamPrivate&, const MediaRecorderPrivateOptions&);
-    explicit MediaRecorderPrivateGStreamer(MediaStreamPrivate&, const MediaRecorderPrivateOptions&);
-    ~MediaRecorderPrivateGStreamer();
+    using SelectTracksCallback = Function<void(MediaRecorderPrivate::AudioVideoSelectedTracks)>;
+    static RefPtr<MediaRecorderPrivateBackend> create(MediaStreamPrivate& stream, const MediaRecorderPrivateOptions& options)
+    {
+        return adoptRef(*new MediaRecorderPrivateBackend(stream, options));
+    }
 
-    static bool isTypeSupported(const ContentType&);
+    ~MediaRecorderPrivateBackend();
 
-protected:
     bool preparePipeline();
 
-private:
-    void videoFrameAvailable(VideoFrame&, VideoFrameTimeMetadata) final { };
-    void audioSamplesAvailable(const WTF::MediaTime&, const PlatformAudioData&, const AudioStreamDescription&, size_t) final { };
+    void fetchData(MediaRecorderPrivate::FetchDataCallback&&);
+    void startRecording(MediaRecorderPrivate::StartRecordingCallback&&);
+    void stopRecording(CompletionHandler<void()>&&);
+    void pauseRecording(CompletionHandler<void()>&&);
+    void resumeRecording(CompletionHandler<void()>&&);
+    const String& mimeType() const;
 
-    void fetchData(FetchDataCallback&&) final;
-    void startRecording(StartRecordingCallback&&) final;
-    void stopRecording(CompletionHandler<void()>&&) final;
-    void pauseRecording(CompletionHandler<void()>&&) final;
-    void resumeRecording(CompletionHandler<void()>&&) final;
-    const String& mimeType() const final;
+    void setSelectTracksCallback(SelectTracksCallback&& callback) { m_selectTracksCallback = WTFMove(callback); }
+
+private:
+    MediaRecorderPrivateBackend(MediaStreamPrivate&, const MediaRecorderPrivateOptions&);
 
     void setSource(GstElement*);
     void setSink(GstElement*);
@@ -88,6 +89,30 @@ private:
 
     MediaStreamPrivate& m_stream;
     const MediaRecorderPrivateOptions& m_options;
+    std::optional<SelectTracksCallback> m_selectTracksCallback;
+};
+
+class MediaRecorderPrivateGStreamer final : public MediaRecorderPrivate {
+    WTF_MAKE_FAST_ALLOCATED;
+public:
+    static std::unique_ptr<MediaRecorderPrivateGStreamer> create(MediaStreamPrivate&, const MediaRecorderPrivateOptions&);
+    explicit MediaRecorderPrivateGStreamer(Ref<MediaRecorderPrivateBackend>&&);
+    ~MediaRecorderPrivateGStreamer() = default;
+
+    static bool isTypeSupported(const ContentType&);
+
+private:
+    void videoFrameAvailable(VideoFrame&, VideoFrameTimeMetadata) final { };
+    void audioSamplesAvailable(const WTF::MediaTime&, const PlatformAudioData&, const AudioStreamDescription&, size_t) final { };
+
+    void fetchData(FetchDataCallback&&) final;
+    void startRecording(StartRecordingCallback&&) final;
+    void stopRecording(CompletionHandler<void()>&&) final;
+    void pauseRecording(CompletionHandler<void()>&&) final;
+    void resumeRecording(CompletionHandler<void()>&&) final;
+    const String& mimeType() const final;
+
+    Ref<MediaRecorderPrivateBackend> m_recorder;
 };
 
 } // namespace WebCore
-- 
2.33.0
