| Differences between
and this patch
- a/Source/WebCore/ChangeLog +56 lines
Lines 1-3 a/Source/WebCore/ChangeLog_sec1
1
2021-04-17  Philippe Normand  <pnormand@igalia.com>
2
3
        [GStreamer][MediaStream] fast/mediastream/play-newly-added-audio-track.html is failing since added in r260380
4
        https://bugs.webkit.org/show_bug.cgi?id=210840
5
6
        Reviewed by Xabier Rodriguez-Calvar.
7
8
        The test was failing mainly because our mediastreamsrc GStreamer element wasn't creating the
9
        corresponding AudioTrackPrivateMediaStream object. The media player should propagate its
10
        volume/mute/is-playing states to the source element. And also until now our mediastreamsrc
11
        element was handling at most one audio track and at most one audio track, but it turns out
12
        multiple audio tracks can be added actually. So I refactored the element internals
13
        accordingly. The InternalSource is now directly observing the corresponding
14
        RealtimeMediaSource, which simplified some code. Observing is also now suspended/resumed
15
        depending on the element state.
16
17
        We don't implement a GStreamer AudioMediaStreamTrackRenderer class yet, because actual
18
        rendering is still performed from the WebProcess, so early returns were added in
19
        AudioTrackPrivateMediaStream to handle this case.
20
21
        * platform/graphics/gstreamer/MediaPlayerPrivateGStreamer.cpp:
22
        (WebCore::MediaPlayerPrivateGStreamer::updateTracks):
23
        (WebCore::MediaPlayerPrivateGStreamer::configureMediaStreamAudioTracks):
24
        (WebCore::MediaPlayerPrivateGStreamer::setVolume):
25
        (WebCore::MediaPlayerPrivateGStreamer::setMuted):
26
        (WebCore::MediaPlayerPrivateGStreamer::updateStates):
27
        (WebCore::MediaPlayerPrivateGStreamer::didEnd):
28
        * platform/graphics/gstreamer/MediaPlayerPrivateGStreamer.h:
29
        * platform/mediastream/AudioTrackPrivateMediaStream.cpp:
30
        (WebCore::AudioTrackPrivateMediaStream::createRenderer):
31
        (WebCore::AudioTrackPrivateMediaStream::setLogger):
32
        (WebCore::AudioTrackPrivateMediaStream::clear):
33
        (WebCore::AudioTrackPrivateMediaStream::setVolume):
34
        (WebCore::AudioTrackPrivateMediaStream::setAudioOutputDevice):
35
        (WebCore::AudioTrackPrivateMediaStream::volume const):
36
        (WebCore::AudioTrackPrivateMediaStream::audioSamplesAvailable):
37
        (WebCore::AudioTrackPrivateMediaStream::startRenderer):
38
        (WebCore::AudioTrackPrivateMediaStream::stopRenderer):
39
        (WebCore::AudioTrackPrivateMediaStream::createNewRenderer):
40
        * platform/mediastream/gstreamer/GStreamerMediaStreamSource.cpp:
41
        (mediaStreamTrackPrivateGetTags):
42
        (WebKitMediaStreamObserver::didRemoveTrack):
43
        (webkitMediaStreamSrcConstructed):
44
        (webkitMediaStreamSrcDispose):
45
        (webkitMediaStreamSrcChangeState):
46
        (webkitMediaStreamSrcPostStreamCollection):
47
        (webkitMediaStreamSrcAddPad):
48
        (ProbeData::ProbeData):
49
        (webkitMediaStreamSrcPadProbeCb):
50
        (webkitMediaStreamSrcAddTrack):
51
        (webkitMediaStreamSrcSetStream):
52
        (webkitMediaStreamSrcTrackEnded):
53
        (InternalSource::trackEnded):
54
        (webkitMediaStreamSrcConfigureAudioTracks):
55
        * platform/mediastream/gstreamer/GStreamerMediaStreamSource.h:
56
1
2021-04-16  Brent Fulgham  <bfulgham@apple.com>
57
2021-04-16  Brent Fulgham  <bfulgham@apple.com>
2
58
3
        Perform port blocking earlier in the load
59
        Perform port blocking earlier in the load
- a/Source/WebCore/platform/graphics/gstreamer/MediaPlayerPrivateGStreamer.cpp -2 / +16 lines
Lines 1511-1519 void MediaPlayerPrivateGStreamer::updateTracks(const GRefPtr<GstStreamCollection a/Source/WebCore/platform/graphics/gstreamer/MediaPlayerPrivateGStreamer.cpp_sec1
1511
1511
1512
        GST_DEBUG_OBJECT(pipeline(), "Inspecting %s track with ID %s", gst_stream_type_get_name(type), streamId.utf8().data());
1512
        GST_DEBUG_OBJECT(pipeline(), "Inspecting %s track with ID %s", gst_stream_type_get_name(type), streamId.utf8().data());
1513
1513
1514
        if (type & GST_STREAM_TYPE_AUDIO)
1514
        if (type & GST_STREAM_TYPE_AUDIO) {
1515
            CREATE_TRACK(audio, Audio);
1515
            CREATE_TRACK(audio, Audio);
1516
        else if (type & GST_STREAM_TYPE_VIDEO && m_player->isVideoPlayer())
1516
            configureMediaStreamAudioTracks();
1517
        } else if (type & GST_STREAM_TYPE_VIDEO && m_player->isVideoPlayer())
1517
            CREATE_TRACK(video, Video);
1518
            CREATE_TRACK(video, Video);
1518
        else if (type & GST_STREAM_TYPE_TEXT && !useMediaSource) {
1519
        else if (type & GST_STREAM_TYPE_TEXT && !useMediaSource) {
1519
            auto track = InbandTextTrackPrivateGStreamer::create(textTrackIndex++, stream);
1520
            auto track = InbandTextTrackPrivateGStreamer::create(textTrackIndex++, stream);
Lines 1653-1658 FloatSize MediaPlayerPrivateGStreamer::naturalSize() const a/Source/WebCore/platform/graphics/gstreamer/MediaPlayerPrivateGStreamer.cpp_sec2
1653
    return m_videoSize;
1654
    return m_videoSize;
1654
}
1655
}
1655
1656
1657
void MediaPlayerPrivateGStreamer::configureMediaStreamAudioTracks()
1658
{
1659
    if (WEBKIT_IS_MEDIA_STREAM_SRC(m_source.get()))
1660
        webkitMediaStreamSrcConfigureAudioTracks(WEBKIT_MEDIA_STREAM_SRC(m_source.get()), volume(), isMuted(), !m_isPaused);
1661
}
1662
1656
void MediaPlayerPrivateGStreamer::setVolume(float volume)
1663
void MediaPlayerPrivateGStreamer::setVolume(float volume)
1657
{
1664
{
1658
    if (!m_volumeElement)
1665
    if (!m_volumeElement)
Lines 1660-1665 void MediaPlayerPrivateGStreamer::setVolume(float volume) a/Source/WebCore/platform/graphics/gstreamer/MediaPlayerPrivateGStreamer.cpp_sec3
1660
1667
1661
    GST_DEBUG_OBJECT(pipeline(), "Setting volume: %f", volume);
1668
    GST_DEBUG_OBJECT(pipeline(), "Setting volume: %f", volume);
1662
    gst_stream_volume_set_volume(m_volumeElement.get(), GST_STREAM_VOLUME_FORMAT_LINEAR, static_cast<double>(volume));
1669
    gst_stream_volume_set_volume(m_volumeElement.get(), GST_STREAM_VOLUME_FORMAT_LINEAR, static_cast<double>(volume));
1670
    configureMediaStreamAudioTracks();
1663
}
1671
}
1664
1672
1665
float MediaPlayerPrivateGStreamer::volume() const
1673
float MediaPlayerPrivateGStreamer::volume() const
Lines 1717-1722 void MediaPlayerPrivateGStreamer::setMuted(bool shouldMute) a/Source/WebCore/platform/graphics/gstreamer/MediaPlayerPrivateGStreamer.cpp_sec4
1717
1725
1718
    GST_INFO_OBJECT(pipeline(), "Setting muted state to %s", boolForPrinting(shouldMute));
1726
    GST_INFO_OBJECT(pipeline(), "Setting muted state to %s", boolForPrinting(shouldMute));
1719
    g_object_set(m_volumeElement.get(), "mute", shouldMute, nullptr);
1727
    g_object_set(m_volumeElement.get(), "mute", shouldMute, nullptr);
1728
    configureMediaStreamAudioTracks();
1720
}
1729
}
1721
1730
1722
void MediaPlayerPrivateGStreamer::notifyPlayerOfMute()
1731
void MediaPlayerPrivateGStreamer::notifyPlayerOfMute()
Lines 2258-2263 void MediaPlayerPrivateGStreamer::updateStates() a/Source/WebCore/platform/graphics/gstreamer/MediaPlayerPrivateGStreamer.cpp_sec5
2258
2267
2259
    MediaPlayer::NetworkState oldNetworkState = m_networkState;
2268
    MediaPlayer::NetworkState oldNetworkState = m_networkState;
2260
    MediaPlayer::ReadyState oldReadyState = m_readyState;
2269
    MediaPlayer::ReadyState oldReadyState = m_readyState;
2270
    bool oldIsPaused = m_isPaused;
2261
    GstState pending, state;
2271
    GstState pending, state;
2262
    bool stateReallyChanged = false;
2272
    bool stateReallyChanged = false;
2263
2273
Lines 2424-2429 void MediaPlayerPrivateGStreamer::updateStates() a/Source/WebCore/platform/graphics/gstreamer/MediaPlayerPrivateGStreamer.cpp_sec6
2424
            }
2434
            }
2425
        }
2435
        }
2426
    }
2436
    }
2437
2438
    if (oldIsPaused != m_isPaused)
2439
        configureMediaStreamAudioTracks();
2427
}
2440
}
2428
2441
2429
void MediaPlayerPrivateGStreamer::mediaLocationChanged(GstMessage* message)
2442
void MediaPlayerPrivateGStreamer::mediaLocationChanged(GstMessage* message)
Lines 2559-2564 void MediaPlayerPrivateGStreamer::didEnd() a/Source/WebCore/platform/graphics/gstreamer/MediaPlayerPrivateGStreamer.cpp_sec7
2559
        m_isPaused = true;
2572
        m_isPaused = true;
2560
        changePipelineState(GST_STATE_READY);
2573
        changePipelineState(GST_STATE_READY);
2561
        m_didDownloadFinish = false;
2574
        m_didDownloadFinish = false;
2575
        configureMediaStreamAudioTracks();
2562
2576
2563
#if USE(WPE_VIDEO_PLANE_DISPLAY_DMABUF)
2577
#if USE(WPE_VIDEO_PLANE_DISPLAY_DMABUF)
2564
        wpe_video_plane_display_dmabuf_source_end_of_stream(m_wpeVideoPlaneDisplayDmaBuf.get());
2578
        wpe_video_plane_display_dmabuf_source_end_of_stream(m_wpeVideoPlaneDisplayDmaBuf.get());
- a/Source/WebCore/platform/graphics/gstreamer/MediaPlayerPrivateGStreamer.h +2 lines
Lines 458-463 private: a/Source/WebCore/platform/graphics/gstreamer/MediaPlayerPrivateGStreamer.h_sec1
458
    bool waitForCDMAttachment();
458
    bool waitForCDMAttachment();
459
#endif
459
#endif
460
460
461
    void configureMediaStreamAudioTracks();
462
461
    Atomic<bool> m_isPlayerShuttingDown;
463
    Atomic<bool> m_isPlayerShuttingDown;
462
    GRefPtr<GstElement> m_textSink;
464
    GRefPtr<GstElement> m_textSink;
463
    GstStructure* m_mediaLocations { nullptr };
465
    GstStructure* m_mediaLocations { nullptr };
- a/Source/WebCore/platform/mediastream/AudioTrackPrivateMediaStream.cpp -9 / +21 lines
Lines 51-56 AudioTrackPrivateMediaStream::~AudioTrackPrivateMediaStream() a/Source/WebCore/platform/mediastream/AudioTrackPrivateMediaStream.cpp_sec1
51
std::unique_ptr<AudioMediaStreamTrackRenderer> AudioTrackPrivateMediaStream::createRenderer(AudioTrackPrivateMediaStream& stream)
51
std::unique_ptr<AudioMediaStreamTrackRenderer> AudioTrackPrivateMediaStream::createRenderer(AudioTrackPrivateMediaStream& stream)
52
{
52
{
53
    auto renderer = AudioMediaStreamTrackRenderer::create();
53
    auto renderer = AudioMediaStreamTrackRenderer::create();
54
    if (!renderer)
55
        return nullptr;
54
    renderer->setCrashCallback([stream = makeWeakPtr(stream)] {
56
    renderer->setCrashCallback([stream = makeWeakPtr(stream)] {
55
        if (stream)
57
        if (stream)
56
            stream->createNewRenderer();
58
            stream->createNewRenderer();
Lines 62-68 std::unique_ptr<AudioMediaStreamTrackRenderer> AudioTrackPrivateMediaStream::cre a/Source/WebCore/platform/mediastream/AudioTrackPrivateMediaStream.cpp_sec2
62
void AudioTrackPrivateMediaStream::setLogger(const Logger& logger, const void* identifier)
64
void AudioTrackPrivateMediaStream::setLogger(const Logger& logger, const void* identifier)
63
{
65
{
64
    TrackPrivateBase::setLogger(logger, identifier);
66
    TrackPrivateBase::setLogger(logger, identifier);
65
    m_renderer->setLogger(logger, identifier);
67
    if (m_renderer)
68
        m_renderer->setLogger(logger, identifier);
66
}
69
}
67
#endif
70
#endif
68
71
Lines 77-83 void AudioTrackPrivateMediaStream::clear() a/Source/WebCore/platform/mediastream/AudioTrackPrivateMediaStream.cpp_sec3
77
        m_audioSource->removeAudioSampleObserver(*this);
80
        m_audioSource->removeAudioSampleObserver(*this);
78
81
79
    streamTrack().removeObserver(*this);
82
    streamTrack().removeObserver(*this);
80
    m_renderer->clear();
83
    if (m_renderer)
84
        m_renderer->clear();
81
}
85
}
82
86
83
void AudioTrackPrivateMediaStream::play()
87
void AudioTrackPrivateMediaStream::play()
Lines 103-126 void AudioTrackPrivateMediaStream::setMuted(bool muted) a/Source/WebCore/platform/mediastream/AudioTrackPrivateMediaStream.cpp_sec4
103
107
104
void AudioTrackPrivateMediaStream::setVolume(float volume)
108
void AudioTrackPrivateMediaStream::setVolume(float volume)
105
{
109
{
106
    m_renderer->setVolume(volume);
110
    if (m_renderer)
111
        m_renderer->setVolume(volume);
107
    updateRenderer();
112
    updateRenderer();
108
}
113
}
109
114
110
void AudioTrackPrivateMediaStream::setAudioOutputDevice(const String& deviceId)
115
void AudioTrackPrivateMediaStream::setAudioOutputDevice(const String& deviceId)
111
{
116
{
112
    m_renderer->setAudioOutputDevice(deviceId);
117
    if (m_renderer)
118
        m_renderer->setAudioOutputDevice(deviceId);
113
}
119
}
114
120
115
float AudioTrackPrivateMediaStream::volume() const
121
float AudioTrackPrivateMediaStream::volume() const
116
{
122
{
117
    return m_renderer->volume();
123
    if (m_renderer)
124
        return m_renderer->volume();
125
    return 1;
118
}
126
}
119
127
120
// May get called on a background thread.
128
// May get called on a background thread.
121
void AudioTrackPrivateMediaStream::audioSamplesAvailable(const MediaTime& sampleTime, const PlatformAudioData& audioData, const AudioStreamDescription& description, size_t sampleCount)
129
void AudioTrackPrivateMediaStream::audioSamplesAvailable(const MediaTime& sampleTime, const PlatformAudioData& audioData, const AudioStreamDescription& description, size_t sampleCount)
122
{
130
{
123
    m_renderer->pushSamples(sampleTime, audioData, description, sampleCount);
131
    if (m_renderer)
132
        m_renderer->pushSamples(sampleTime, audioData, description, sampleCount);
124
}
133
}
125
134
126
void AudioTrackPrivateMediaStream::trackMutedChanged(MediaStreamTrackPrivate&)
135
void AudioTrackPrivateMediaStream::trackMutedChanged(MediaStreamTrackPrivate&)
Lines 154-160 void AudioTrackPrivateMediaStream::startRenderer() a/Source/WebCore/platform/mediastream/AudioTrackPrivateMediaStream.cpp_sec5
154
        return;
163
        return;
155
164
156
    m_isPlaying = true;
165
    m_isPlaying = true;
157
    m_renderer->start();
166
    if (m_renderer)
167
        m_renderer->start();
158
    m_audioSource->addAudioSampleObserver(*this);
168
    m_audioSource->addAudioSampleObserver(*this);
159
}
169
}
160
170
Lines 166-172 void AudioTrackPrivateMediaStream::stopRenderer() a/Source/WebCore/platform/mediastream/AudioTrackPrivateMediaStream.cpp_sec6
166
176
167
    m_isPlaying = false;
177
    m_isPlaying = false;
168
    m_audioSource->removeAudioSampleObserver(*this);
178
    m_audioSource->removeAudioSampleObserver(*this);
169
    m_renderer->stop();
179
    if (m_renderer)
180
        m_renderer->stop();
170
}
181
}
171
182
172
void AudioTrackPrivateMediaStream::createNewRenderer()
183
void AudioTrackPrivateMediaStream::createNewRenderer()
Lines 176-182 void AudioTrackPrivateMediaStream::createNewRenderer() a/Source/WebCore/platform/mediastream/AudioTrackPrivateMediaStream.cpp_sec7
176
187
177
    float volume = this->volume();
188
    float volume = this->volume();
178
    m_renderer = createRenderer(*this);
189
    m_renderer = createRenderer(*this);
179
    m_renderer->setVolume(volume);
190
    if (m_renderer)
191
        m_renderer->setVolume(volume);
180
192
181
    if (isPlaying)
193
    if (isPlaying)
182
        startRenderer();
194
        startRenderer();
- a/Source/WebCore/platform/mediastream/gstreamer/GStreamerMediaStreamSource.cpp -237 / +239 lines
Lines 26-38 a/Source/WebCore/platform/mediastream/gstreamer/GStreamerMediaStreamSource.cpp_sec1
26
26
27
#if ENABLE(VIDEO) && ENABLE(MEDIA_STREAM) && USE(GSTREAMER)
27
#if ENABLE(VIDEO) && ENABLE(MEDIA_STREAM) && USE(GSTREAMER)
28
28
29
#include "AudioTrackPrivate.h"
29
#include "AudioTrackPrivateMediaStream.h"
30
#include "GStreamerAudioData.h"
30
#include "GStreamerAudioData.h"
31
#include "GStreamerCommon.h"
31
#include "GStreamerCommon.h"
32
#include "MediaSampleGStreamer.h"
32
#include "MediaSampleGStreamer.h"
33
#include "MediaStreamPrivate.h"
33
#include "MediaStreamPrivate.h"
34
#include "MediaStreamTrackPrivate.h"
34
#include "VideoTrackPrivateMediaStream.h"
35
#include "VideoTrackPrivate.h"
36
35
37
#include <gst/app/gstappsrc.h>
36
#include <gst/app/gstappsrc.h>
38
#include <wtf/UUID.h>
37
#include <wtf/UUID.h>
Lines 40-63 a/Source/WebCore/platform/mediastream/gstreamer/GStreamerMediaStreamSource.cpp_sec2
40
39
41
using namespace WebCore;
40
using namespace WebCore;
42
41
43
static void webkitMediaStreamSrcPushVideoSample(WebKitMediaStreamSrc*, GstSample*);
42
static GstStaticPadTemplate videoSrcTemplate = GST_STATIC_PAD_TEMPLATE("video_src%u", GST_PAD_SRC, GST_PAD_SOMETIMES,
44
static void webkitMediaStreamSrcPushAudioSample(WebKitMediaStreamSrc*, const GRefPtr<GstSample>&);
45
static void webkitMediaStreamSrcTrackEnded(WebKitMediaStreamSrc*, MediaStreamTrackPrivate&);
46
static void webkitMediaStreamSrcRemoveTrackByType(WebKitMediaStreamSrc*, RealtimeMediaSource::Type);
47
static void webkitMediaStreamSrcRemoveTrackObserver(WebKitMediaStreamSrc*, MediaStreamTrackPrivate&);
48
static void webkitMediaStreamSrcPostStreamCollection(WebKitMediaStreamSrc*);
49
static void webkitMediaStreamSrcRemovePad(WebKitMediaStreamSrc*, const char* padName);
50
51
static GstStaticPadTemplate videoSrcTemplate = GST_STATIC_PAD_TEMPLATE("video_src", GST_PAD_SRC, GST_PAD_SOMETIMES,
52
    GST_STATIC_CAPS("video/x-raw;video/x-h264;video/x-vp8"));
43
    GST_STATIC_CAPS("video/x-raw;video/x-h264;video/x-vp8"));
53
44
54
static GstStaticPadTemplate audioSrcTemplate = GST_STATIC_PAD_TEMPLATE("audio_src", GST_PAD_SRC, GST_PAD_SOMETIMES,
45
static GstStaticPadTemplate audioSrcTemplate = GST_STATIC_PAD_TEMPLATE("audio_src%u", GST_PAD_SRC, GST_PAD_SOMETIMES,
55
    GST_STATIC_CAPS("audio/x-raw(ANY);"));
46
    GST_STATIC_CAPS("audio/x-raw(ANY);"));
56
47
57
GST_DEBUG_CATEGORY_STATIC(webkitMediaStreamSrcDebug);
48
GST_DEBUG_CATEGORY_STATIC(webkitMediaStreamSrcDebug);
58
#define GST_CAT_DEFAULT webkitMediaStreamSrcDebug
49
#define GST_CAT_DEFAULT webkitMediaStreamSrcDebug
59
50
60
GRefPtr<GstTagList> mediaStreamTrackPrivateGetTags(MediaStreamTrackPrivate* track)
51
GRefPtr<GstTagList> mediaStreamTrackPrivateGetTags(const MediaStreamTrackPrivate* track)
61
{
52
{
62
    auto tagList = adoptGRef(gst_tag_list_new_empty());
53
    auto tagList = adoptGRef(gst_tag_list_new_empty());
63
54
Lines 100-154 GstStream* webkitMediaStreamNew(MediaStreamTrackPrivate* track) a/Source/WebCore/platform/mediastream/gstreamer/GStreamerMediaStreamSource.cpp_sec3
100
    return stream;
91
    return stream;
101
}
92
}
102
93
103
class WebKitMediaStreamTrackObserver
104
    : public MediaStreamTrackPrivate::Observer
105
    , public RealtimeMediaSource::AudioSampleObserver
106
    , public RealtimeMediaSource::VideoSampleObserver {
107
    WTF_MAKE_FAST_ALLOCATED;
108
public:
109
    virtual ~WebKitMediaStreamTrackObserver() { };
110
    WebKitMediaStreamTrackObserver(GstElement* src)
111
        : m_src(src) { }
112
    void trackStarted(MediaStreamTrackPrivate&) final { };
113
114
    void trackEnded(MediaStreamTrackPrivate& track) final
115
    {
116
        if (m_src)
117
            webkitMediaStreamSrcTrackEnded(WEBKIT_MEDIA_STREAM_SRC(m_src), track);
118
    }
119
120
    void trackEnabledChanged(MediaStreamTrackPrivate& track) final
121
    {
122
        m_enabled = track.enabled();
123
    }
124
125
    void trackMutedChanged(MediaStreamTrackPrivate&) final { };
126
    void trackSettingsChanged(MediaStreamTrackPrivate&) final { };
127
    void readyStateChanged(MediaStreamTrackPrivate&) final { };
128
129
    void videoSampleAvailable(MediaSample& sample) final
130
    {
131
        if (!m_enabled || !m_src)
132
            return;
133
134
        auto* gstSample = static_cast<MediaSampleGStreamer*>(&sample)->platformSample().sample.gstSample;
135
        webkitMediaStreamSrcPushVideoSample(WEBKIT_MEDIA_STREAM_SRC(m_src), gstSample);
136
    }
137
138
    void audioSamplesAvailable(const MediaTime&, const PlatformAudioData& audioData, const AudioStreamDescription&, size_t) final
139
    {
140
        if (!m_enabled || !m_src)
141
            return;
142
143
        auto data = static_cast<const GStreamerAudioData&>(audioData);
144
        webkitMediaStreamSrcPushAudioSample(WEBKIT_MEDIA_STREAM_SRC(m_src), data.getSample());
145
    }
146
147
private:
148
    GstElement* m_src { nullptr };
149
    bool m_enabled { true };
150
};
151
152
class WebKitMediaStreamObserver : public MediaStreamPrivate::Observer {
94
class WebKitMediaStreamObserver : public MediaStreamPrivate::Observer {
153
    WTF_MAKE_FAST_ALLOCATED;
95
    WTF_MAKE_FAST_ALLOCATED;
154
public:
96
public:
Lines 166-193 public: a/Source/WebCore/platform/mediastream/gstreamer/GStreamerMediaStreamSource.cpp_sec4
166
    void didAddTrack(MediaStreamTrackPrivate& track) final
108
    void didAddTrack(MediaStreamTrackPrivate& track) final
167
    {
109
    {
168
        if (m_src)
110
        if (m_src)
169
            webkitMediaStreamSrcAddTrack(WEBKIT_MEDIA_STREAM_SRC(m_src), &track, false);
111
            webkitMediaStreamSrcAddTrack(WEBKIT_MEDIA_STREAM_SRC_CAST(m_src), &track, false);
170
    }
112
    }
171
113
172
    void didRemoveTrack(MediaStreamTrackPrivate& track) final
114
    void didRemoveTrack(MediaStreamTrackPrivate&) final;
173
    {
174
        if (!m_src)
175
            return;
176
177
        webkitMediaStreamSrcRemoveTrackObserver(WEBKIT_MEDIA_STREAM_SRC(m_src), track);
178
        webkitMediaStreamSrcRemoveTrackByType(WEBKIT_MEDIA_STREAM_SRC(m_src), track.type());
179
    }
180
115
181
private:
116
private:
182
    GstElement* m_src;
117
    GstElement* m_src;
183
};
118
};
184
119
185
class InternalSource {
120
class InternalSource final : public MediaStreamTrackPrivate::Observer,
121
    public RealtimeMediaSource::AudioSampleObserver,
122
    public RealtimeMediaSource::VideoSampleObserver {
186
    WTF_MAKE_FAST_ALLOCATED;
123
    WTF_MAKE_FAST_ALLOCATED;
187
public:
124
public:
188
    InternalSource(bool isCaptureTrack, RealtimeMediaSource::Type type)
125
    InternalSource(GstElement* parent, MediaStreamTrackPrivate& track, const String& padName)
189
        : m_type(type)
126
        : m_parent(parent)
127
        , m_track(track)
128
        , m_padName(padName)
190
    {
129
    {
130
        if (track.type() == RealtimeMediaSource::Type::Audio)
131
            m_audioTrack = AudioTrackPrivateMediaStream::create(track);
132
        else if (track.type() == RealtimeMediaSource::Type::Video)
133
            m_videoTrack = VideoTrackPrivateMediaStream::create(track);
134
135
        bool isCaptureTrack = track.isCaptureTrack();
191
        m_src = gst_element_factory_make("appsrc", nullptr);
136
        m_src = gst_element_factory_make("appsrc", nullptr);
192
        RELEASE_ASSERT_WITH_MESSAGE(GST_IS_APP_SRC(m_src.get()), "GStreamer appsrc element not found. Please make sure to install gst-plugins-base");
137
        RELEASE_ASSERT_WITH_MESSAGE(GST_IS_APP_SRC(m_src.get()), "GStreamer appsrc element not found. Please make sure to install gst-plugins-base");
193
138
Lines 201-225 public: a/Source/WebCore/platform/mediastream/gstreamer/GStreamerMediaStreamSource.cpp_sec5
201
        }), this);
146
        }), this);
202
    }
147
    }
203
148
204
    ~InternalSource()
149
    virtual ~InternalSource()
205
    {
150
    {
206
        g_signal_handlers_disconnect_matched(m_src.get(), G_SIGNAL_MATCH_DATA, 0, 0, nullptr, nullptr, this);
151
        stopObserving();
207
152
208
        if (auto parent = adoptGRef(gst_object_get_parent(GST_OBJECT_CAST(m_src.get())))) {
153
        if (m_src)
209
            GST_STATE_LOCK(GST_ELEMENT_CAST(parent.get()));
154
            g_signal_handlers_disconnect_matched(m_src.get(), G_SIGNAL_MATCH_DATA, 0, 0, nullptr, nullptr, this);
210
            gst_element_set_locked_state(m_src.get(), true);
211
            gst_element_set_state(m_src.get(), GST_STATE_NULL);
212
            gst_bin_remove(GST_BIN_CAST(parent.get()), m_src.get());
213
            gst_element_set_locked_state(m_src.get(), false);
214
            GST_STATE_UNLOCK(GST_ELEMENT_CAST(parent.get()));
215
216
            const char* padName = m_type == RealtimeMediaSource::Type::Video ? "video_src" : "audio_src";
217
            webkitMediaStreamSrcRemovePad(WEBKIT_MEDIA_STREAM_SRC(parent.get()), padName);
218
        }
219
    }
155
    }
220
156
157
    const MediaStreamTrackPrivate& track() const { return m_track; }
158
    const String& padName() const { return m_padName; }
221
    GstElement* get() const { return m_src.get(); }
159
    GstElement* get() const { return m_src.get(); }
222
160
161
    void startObserving()
162
    {
163
        if (m_isObserving)
164
            return;
165
166
        m_track.addObserver(*this);
167
        switch (m_track.type()) {
168
        case RealtimeMediaSource::Type::Audio:
169
            m_track.source().addAudioSampleObserver(*this);
170
            break;
171
        case RealtimeMediaSource::Type::Video:
172
            m_track.source().addVideoSampleObserver(*this);
173
            break;
174
        case RealtimeMediaSource::Type::None:
175
            ASSERT_NOT_REACHED();
176
        }
177
        m_isObserving = true;
178
    }
179
180
    void stopObserving()
181
    {
182
        if (!m_isObserving)
183
            return;
184
185
        m_isObserving = false;
186
        switch (m_track.type()) {
187
        case RealtimeMediaSource::Type::Audio:
188
            m_track.source().removeAudioSampleObserver(*this);
189
            break;
190
        case RealtimeMediaSource::Type::Video:
191
            m_track.source().removeVideoSampleObserver(*this);
192
            break;
193
        case RealtimeMediaSource::Type::None:
194
            ASSERT_NOT_REACHED();
195
        }
196
        m_track.removeObserver(*this);
197
    }
198
199
    void configureAudioTrack(float volume, bool isMuted, bool isPlaying)
200
    {
201
        ASSERT(m_track.type() == RealtimeMediaSource::Type::Audio);
202
        m_audioTrack->setVolume(volume);
203
        m_audioTrack->setMuted(isMuted);
204
        m_audioTrack->setEnabled(m_audioTrack->streamTrack().enabled() && !m_audioTrack->streamTrack().muted());
205
        if (isPlaying)
206
            m_audioTrack->play();
207
    }
208
223
    void pushSample(GstSample* sample)
209
    void pushSample(GstSample* sample)
224
    {
210
    {
225
        ASSERT(m_src);
211
        ASSERT(m_src);
Lines 235-246 public: a/Source/WebCore/platform/mediastream/gstreamer/GStreamerMediaStreamSource.cpp_sec6
235
            gst_pad_set_offset(pad.get(), -m_firstBufferPts);
221
            gst_pad_set_offset(pad.get(), -m_firstBufferPts);
236
        }
222
        }
237
223
238
        if (m_type == RealtimeMediaSource::Type::Video && drop)
224
        if (m_track.type() == RealtimeMediaSource::Type::Video && drop)
239
            drop = doCapsHaveType(caps, "video/x-raw") || GST_BUFFER_FLAG_IS_SET(buffer, GST_BUFFER_FLAG_DELTA_UNIT);
225
            drop = doCapsHaveType(caps, "video/x-raw") || GST_BUFFER_FLAG_IS_SET(buffer, GST_BUFFER_FLAG_DELTA_UNIT);
240
226
241
        if (drop) {
227
        if (drop) {
242
            m_needsDiscont = true;
228
            m_needsDiscont = true;
243
            GST_INFO_OBJECT(m_src.get(), "%s queue full already... not pushing", m_type == RealtimeMediaSource::Type::Video ? "Video" : "Audio");
229
            GST_INFO_OBJECT(m_src.get(), "%s queue full already... not pushing", m_track.type() == RealtimeMediaSource::Type::Video ? "Video" : "Audio");
244
            return;
230
            return;
245
        }
231
        }
246
232
Lines 252-275 public: a/Source/WebCore/platform/mediastream/gstreamer/GStreamerMediaStreamSource.cpp_sec7
252
        gst_app_src_push_sample(GST_APP_SRC(m_src.get()), sample);
238
        gst_app_src_push_sample(GST_APP_SRC(m_src.get()), sample);
253
    }
239
    }
254
240
241
    void trackStarted(MediaStreamTrackPrivate&) final { };
242
    void trackEnded(MediaStreamTrackPrivate&) final;
243
244
    void trackEnabledChanged(MediaStreamTrackPrivate& track) final
245
    {
246
        m_enabled = track.enabled();
247
    }
248
249
    void trackMutedChanged(MediaStreamTrackPrivate&) final { };
250
    void trackSettingsChanged(MediaStreamTrackPrivate&) final { };
251
    void readyStateChanged(MediaStreamTrackPrivate&) final { };
252
253
    void videoSampleAvailable(MediaSample& sample) final
254
    {
255
        if (!m_enabled || !m_parent)
256
            return;
257
258
        auto* gstSample = static_cast<MediaSampleGStreamer*>(&sample)->platformSample().sample.gstSample;
259
        pushSample(gstSample);
260
    }
261
262
    void audioSamplesAvailable(const MediaTime&, const PlatformAudioData& audioData, const AudioStreamDescription&, size_t) final
263
    {
264
        if (!m_enabled || !m_parent)
265
            return;
266
267
        const auto& data = static_cast<const GStreamerAudioData&>(audioData);
268
        auto sample = data.getSample();
269
        pushSample(sample.get());
270
    }
271
255
private:
272
private:
256
    RealtimeMediaSource::Type m_type { RealtimeMediaSource::Type::None };
273
    GstElement* m_parent { nullptr };
274
    MediaStreamTrackPrivate& m_track;
257
    GRefPtr<GstElement> m_src;
275
    GRefPtr<GstElement> m_src;
276
    bool m_enabled { true };
258
    GstClockTime m_firstBufferPts { GST_CLOCK_TIME_NONE };
277
    GstClockTime m_firstBufferPts { GST_CLOCK_TIME_NONE };
259
    bool m_enoughData { false };
278
    bool m_enoughData { false };
260
    bool m_needsDiscont { false };
279
    bool m_needsDiscont { false };
280
    String m_padName;
281
    bool m_isObserving { false };
282
    RefPtr<AudioTrackPrivateMediaStream> m_audioTrack;
283
    RefPtr<VideoTrackPrivateMediaStream> m_videoTrack;
261
};
284
};
262
285
263
struct _WebKitMediaStreamSrcPrivate {
286
struct _WebKitMediaStreamSrcPrivate {
264
    CString uri;
287
    CString uri;
265
    Optional<InternalSource> audioSrc;
288
    Vector<std::unique_ptr<InternalSource>> sources;
266
    Optional<InternalSource> videoSrc;
267
    std::unique_ptr<WebKitMediaStreamTrackObserver> mediaStreamTrackObserver;
268
    std::unique_ptr<WebKitMediaStreamObserver> mediaStreamObserver;
289
    std::unique_ptr<WebKitMediaStreamObserver> mediaStreamObserver;
269
    RefPtr<MediaStreamPrivate> stream;
290
    RefPtr<MediaStreamPrivate> stream;
270
    Vector<RefPtr<MediaStreamTrackPrivate>> tracks;
291
    Vector<RefPtr<MediaStreamTrackPrivate>> tracks;
271
    GUniquePtr<GstFlowCombiner> flowCombiner;
292
    GUniquePtr<GstFlowCombiner> flowCombiner;
272
    GRefPtr<GstStreamCollection> streamCollection;
293
    GRefPtr<GstStreamCollection> streamCollection;
294
    Atomic<unsigned> audioPadCounter;
295
    Atomic<unsigned> videoPadCounter;
273
};
296
};
274
297
275
enum {
298
enum {
Lines 278-283 enum { a/Source/WebCore/platform/mediastream/gstreamer/GStreamerMediaStreamSource.cpp_sec8
278
    PROP_LAST
301
    PROP_LAST
279
};
302
};
280
303
304
void WebKitMediaStreamObserver::didRemoveTrack(MediaStreamTrackPrivate& track)
305
{
306
    if (!m_src)
307
        return;
308
309
    auto* element = WEBKIT_MEDIA_STREAM_SRC_CAST(m_src);
310
    element->priv->sources.removeFirstMatching([&](auto& item) {
311
        return item->track().id() == track.id();
312
    });
313
}
314
281
static GstURIType webkitMediaStreamSrcUriGetType(GType)
315
static GstURIType webkitMediaStreamSrcUriGetType(GType)
282
{
316
{
283
    return GST_URI_SRC;
317
    return GST_URI_SRC;
Lines 291-303 static const char* const* webkitMediaStreamSrcUriGetProtocols(GType) a/Source/WebCore/platform/mediastream/gstreamer/GStreamerMediaStreamSource.cpp_sec9
291
325
292
static char* webkitMediaStreamSrcUriGetUri(GstURIHandler* handler)
326
static char* webkitMediaStreamSrcUriGetUri(GstURIHandler* handler)
293
{
327
{
294
    WebKitMediaStreamSrc* self = WEBKIT_MEDIA_STREAM_SRC(handler);
328
    WebKitMediaStreamSrc* self = WEBKIT_MEDIA_STREAM_SRC_CAST(handler);
295
    return g_strdup(self->priv->uri.data());
329
    return g_strdup(self->priv->uri.data());
296
}
330
}
297
331
298
static gboolean webkitMediaStreamSrcUriSetUri(GstURIHandler* handler, const char* uri, GError**)
332
static gboolean webkitMediaStreamSrcUriSetUri(GstURIHandler* handler, const char* uri, GError**)
299
{
333
{
300
    WebKitMediaStreamSrc* self = WEBKIT_MEDIA_STREAM_SRC(handler);
334
    WebKitMediaStreamSrc* self = WEBKIT_MEDIA_STREAM_SRC_CAST(handler);
301
    self->priv->uri = CString(uri);
335
    self->priv->uri = CString(uri);
302
    return TRUE;
336
    return TRUE;
303
}
337
}
Lines 345-354 static void webkitMediaStreamSrcGetProperty(GObject* object, guint propertyId, G a/Source/WebCore/platform/mediastream/gstreamer/GStreamerMediaStreamSource.cpp_sec10
345
static void webkitMediaStreamSrcConstructed(GObject* object)
379
static void webkitMediaStreamSrcConstructed(GObject* object)
346
{
380
{
347
    GST_CALL_PARENT(G_OBJECT_CLASS, constructed, (object));
381
    GST_CALL_PARENT(G_OBJECT_CLASS, constructed, (object));
348
    WebKitMediaStreamSrc* self = WEBKIT_MEDIA_STREAM_SRC(object);
382
    WebKitMediaStreamSrc* self = WEBKIT_MEDIA_STREAM_SRC_CAST(object);
349
    auto* priv = self->priv;
383
    auto* priv = self->priv;
350
384
351
    priv->mediaStreamTrackObserver = makeUnique<WebKitMediaStreamTrackObserver>(GST_ELEMENT_CAST(self));
352
    priv->mediaStreamObserver = makeUnique<WebKitMediaStreamObserver>(GST_ELEMENT_CAST(self));
385
    priv->mediaStreamObserver = makeUnique<WebKitMediaStreamObserver>(GST_ELEMENT_CAST(self));
353
    priv->flowCombiner = GUniquePtr<GstFlowCombiner>(gst_flow_combiner_new());
386
    priv->flowCombiner = GUniquePtr<GstFlowCombiner>(gst_flow_combiner_new());
354
387
Lines 357-392 static void webkitMediaStreamSrcConstructed(GObject* object) a/Source/WebCore/platform/mediastream/gstreamer/GStreamerMediaStreamSource.cpp_sec11
357
    ASSERT(g_object_is_floating(self));
390
    ASSERT(g_object_is_floating(self));
358
}
391
}
359
392
360
static void webkitMediaStreamSrcRemoveTrackObserver(WebKitMediaStreamSrc* self, MediaStreamTrackPrivate& track)
361
{
362
    auto* priv = self->priv;
363
    switch (track.type()) {
364
    case RealtimeMediaSource::Type::Video:
365
        track.source().removeVideoSampleObserver(*priv->mediaStreamTrackObserver);
366
        break;
367
    case RealtimeMediaSource::Type::Audio:
368
        track.source().removeAudioSampleObserver(*priv->mediaStreamTrackObserver);
369
        break;
370
    case RealtimeMediaSource::Type::None:
371
        ASSERT_NOT_REACHED();
372
    }
373
    track.removeObserver(*priv->mediaStreamTrackObserver);
374
}
375
376
static void stopObservingTracks(WebKitMediaStreamSrc* self)
377
{
378
    GST_OBJECT_LOCK(self);
379
    auto* priv = self->priv;
380
    for (auto& track : priv->tracks)
381
        webkitMediaStreamSrcRemoveTrackObserver(self, *track);
382
    GST_OBJECT_UNLOCK(self);
383
}
384
385
static void webkitMediaStreamSrcDispose(GObject* object)
393
static void webkitMediaStreamSrcDispose(GObject* object)
386
{
394
{
387
    WebKitMediaStreamSrc* self = WEBKIT_MEDIA_STREAM_SRC(object);
395
    WebKitMediaStreamSrc* self = WEBKIT_MEDIA_STREAM_SRC_CAST(object);
388
389
    stopObservingTracks(self);
390
396
391
    GST_OBJECT_LOCK(self);
397
    GST_OBJECT_LOCK(self);
392
    auto* priv = self->priv;
398
    auto* priv = self->priv;
Lines 402-415 static void webkitMediaStreamSrcDispose(GObject* object) a/Source/WebCore/platform/mediastream/gstreamer/GStreamerMediaStreamSource.cpp_sec12
402
static GstStateChangeReturn webkitMediaStreamSrcChangeState(GstElement* element, GstStateChange transition)
408
static GstStateChangeReturn webkitMediaStreamSrcChangeState(GstElement* element, GstStateChange transition)
403
{
409
{
404
    GST_DEBUG_OBJECT(element, "%s", gst_state_change_get_name(transition));
410
    GST_DEBUG_OBJECT(element, "%s", gst_state_change_get_name(transition));
411
    WebKitMediaStreamSrc* self = WEBKIT_MEDIA_STREAM_SRC_CAST(element);
405
412
406
    if (transition == GST_STATE_CHANGE_PAUSED_TO_READY)
413
    if (transition == GST_STATE_CHANGE_PAUSED_TO_READY) {
407
        stopObservingTracks(WEBKIT_MEDIA_STREAM_SRC(element));
414
        GST_OBJECT_LOCK(self);
415
        for (auto& item : self->priv->sources)
416
            item->stopObserving();
417
        GST_OBJECT_UNLOCK(self);
418
    }
408
419
409
    GstStateChangeReturn result = GST_ELEMENT_CLASS(webkit_media_stream_src_parent_class)->change_state(element, transition);
420
    GstStateChangeReturn result = GST_ELEMENT_CLASS(webkit_media_stream_src_parent_class)->change_state(element, transition);
410
421
411
    if (transition == GST_STATE_CHANGE_READY_TO_PAUSED)
422
    if (transition == GST_STATE_CHANGE_READY_TO_PAUSED) {
423
        GST_OBJECT_LOCK(self);
424
        for (auto& item : self->priv->sources)
425
            item->startObserving();
426
        GST_OBJECT_UNLOCK(self);
412
        result = GST_STATE_CHANGE_NO_PREROLL;
427
        result = GST_STATE_CHANGE_NO_PREROLL;
428
    }
413
429
414
    return result;
430
    return result;
415
}
431
}
Lines 436-442 static GstFlowReturn webkitMediaStreamSrcChain(GstPad* pad, GstObject* parent, G a/Source/WebCore/platform/mediastream/gstreamer/GStreamerMediaStreamSource.cpp_sec13
436
{
452
{
437
    GRefPtr<GstElement> element = adoptGRef(GST_ELEMENT_CAST(gst_object_get_parent(parent)));
453
    GRefPtr<GstElement> element = adoptGRef(GST_ELEMENT_CAST(gst_object_get_parent(parent)));
438
    GstFlowReturn chainResult = gst_proxy_pad_chain_default(pad, GST_OBJECT_CAST(element.get()), buffer);
454
    GstFlowReturn chainResult = gst_proxy_pad_chain_default(pad, GST_OBJECT_CAST(element.get()), buffer);
439
    auto* self = WEBKIT_MEDIA_STREAM_SRC(element.get());
455
    auto* self = WEBKIT_MEDIA_STREAM_SRC_CAST(element.get());
440
    GstFlowReturn result = gst_flow_combiner_update_pad_flow(self->priv->flowCombiner.get(), pad, chainResult);
456
    GstFlowReturn result = gst_flow_combiner_update_pad_flow(self->priv->flowCombiner.get(), pad, chainResult);
441
457
442
    if (result == GST_FLOW_FLUSHING)
458
    if (result == GST_FLOW_FLUSHING)
Lines 445-456 static GstFlowReturn webkitMediaStreamSrcChain(GstPad* pad, GstObject* parent, G a/Source/WebCore/platform/mediastream/gstreamer/GStreamerMediaStreamSource.cpp_sec14
445
    return result;
461
    return result;
446
}
462
}
447
463
448
static void webkitMediaStreamSrcAddPad(WebKitMediaStreamSrc* self, GstPad* target, GstStaticPadTemplate* padTemplate, GRefPtr<GstTagList>&& tags, RealtimeMediaSource::Type sourceType)
464
static void webkitMediaStreamSrcPostStreamCollection(WebKitMediaStreamSrc* self)
449
{
465
{
466
    auto* priv = self->priv;
467
468
    GST_OBJECT_LOCK(self);
469
    if (priv->stream && (!priv->stream->active() || !priv->stream->hasTracks())) {
470
        GST_OBJECT_UNLOCK(self);
471
        return;
472
    }
473
474
    auto upstreamId = priv->stream ? priv->stream->id() : createCanonicalUUIDString();
475
    priv->streamCollection = adoptGRef(gst_stream_collection_new(upstreamId.ascii().data()));
476
    for (auto& track : priv->tracks) {
477
        if (!track->isActive())
478
            continue;
479
        gst_stream_collection_add_stream(priv->streamCollection.get(), webkitMediaStreamNew(track.get()));
480
    }
481
482
    GST_OBJECT_UNLOCK(self);
483
484
    GST_DEBUG_OBJECT(self, "Posting stream collection");
485
    gst_element_post_message(GST_ELEMENT_CAST(self), gst_message_new_stream_collection(GST_OBJECT_CAST(self), priv->streamCollection.get()));
486
}
487
488
static void webkitMediaStreamSrcAddPad(WebKitMediaStreamSrc* self, GstPad* target, GstStaticPadTemplate* padTemplate, GRefPtr<GstTagList>&& tags, RealtimeMediaSource::Type sourceType, const String& padName)
489
{
490
    ASSERT(sourceType != RealtimeMediaSource::Type::None);
491
    if (sourceType == RealtimeMediaSource::Type::None)
492
        return;
493
450
    GST_DEBUG_OBJECT(self, "%s Ghosting %" GST_PTR_FORMAT, gst_object_get_path_string(GST_OBJECT_CAST(self)), target);
494
    GST_DEBUG_OBJECT(self, "%s Ghosting %" GST_PTR_FORMAT, gst_object_get_path_string(GST_OBJECT_CAST(self)), target);
451
495
452
    const char* padName = sourceType == RealtimeMediaSource::Type::Audio ? "audio_src" : "video_src";
496
    auto* ghostPad = webkitGstGhostPadFromStaticTemplate(padTemplate, padName.ascii().data(), target);
453
    auto* ghostPad = webkitGstGhostPadFromStaticTemplate(padTemplate, padName, target);
454
    gst_pad_set_active(ghostPad, TRUE);
497
    gst_pad_set_active(ghostPad, TRUE);
455
    gst_element_add_pad(GST_ELEMENT_CAST(self), ghostPad);
498
    gst_element_add_pad(GST_ELEMENT_CAST(self), ghostPad);
456
499
Lines 461-487 static void webkitMediaStreamSrcAddPad(WebKitMediaStreamSrc* self, GstPad* targe a/Source/WebCore/platform/mediastream/gstreamer/GStreamerMediaStreamSource.cpp_sec15
461
    gst_pad_push_event(target, gst_event_new_tag(tags.leakRef()));
504
    gst_pad_push_event(target, gst_event_new_tag(tags.leakRef()));
462
}
505
}
463
506
464
static void webkitMediaStreamSrcRemovePad(WebKitMediaStreamSrc* self, const char* padName)
465
{
466
    auto pad = adoptGRef(gst_element_get_static_pad(GST_ELEMENT_CAST(self), padName));
467
    if (!pad)
468
        return;
469
470
    auto proxyPad = adoptGRef(GST_PAD_CAST(gst_proxy_pad_get_internal(GST_PROXY_PAD(pad.get()))));
471
    if (proxyPad)
472
        gst_flow_combiner_remove_pad(self->priv->flowCombiner.get(), proxyPad.get());
473
474
    gst_pad_set_active(pad.get(), FALSE);
475
    gst_element_remove_pad(GST_ELEMENT_CAST(self), pad.get());
476
}
477
478
struct ProbeData {
507
struct ProbeData {
479
    ProbeData(GstElement* element, GstStaticPadTemplate* padTemplate, GRefPtr<GstTagList>&& tags, const char* trackId, RealtimeMediaSource::Type sourceType)
508
    ProbeData(GstElement* element, GstStaticPadTemplate* padTemplate, GRefPtr<GstTagList>&& tags, const char* trackId, RealtimeMediaSource::Type sourceType, const String& padName)
480
        : element(element)
509
        : element(element)
481
        , padTemplate(padTemplate)
510
        , padTemplate(padTemplate)
482
        , tags(WTFMove(tags))
511
        , tags(WTFMove(tags))
483
        , trackId(g_strdup(trackId))
512
        , trackId(g_strdup(trackId))
484
        , sourceType(sourceType)
513
        , sourceType(sourceType)
514
        , padName(padName)
485
    {
515
    {
486
    }
516
    }
487
517
Lines 490-501 struct ProbeData { a/Source/WebCore/platform/mediastream/gstreamer/GStreamerMediaStreamSource.cpp_sec16
490
    GRefPtr<GstTagList> tags;
520
    GRefPtr<GstTagList> tags;
491
    GUniquePtr<char> trackId;
521
    GUniquePtr<char> trackId;
492
    RealtimeMediaSource::Type sourceType;
522
    RealtimeMediaSource::Type sourceType;
523
    String padName;
493
};
524
};
494
525
495
static GstPadProbeReturn webkitMediaStreamSrcPadProbeCb(GstPad* pad, GstPadProbeInfo* info, ProbeData* data)
526
static GstPadProbeReturn webkitMediaStreamSrcPadProbeCb(GstPad* pad, GstPadProbeInfo* info, ProbeData* data)
496
{
527
{
497
    GstEvent* event = GST_PAD_PROBE_INFO_EVENT(info);
528
    GstEvent* event = GST_PAD_PROBE_INFO_EVENT(info);
498
    WebKitMediaStreamSrc* self = WEBKIT_MEDIA_STREAM_SRC(data->element.get());
529
    WebKitMediaStreamSrc* self = WEBKIT_MEDIA_STREAM_SRC_CAST(data->element.get());
499
530
500
    GST_DEBUG_OBJECT(self, "Event %" GST_PTR_FORMAT, event);
531
    GST_DEBUG_OBJECT(self, "Event %" GST_PTR_FORMAT, event);
501
    switch (GST_EVENT_TYPE(event)) {
532
    switch (GST_EVENT_TYPE(event)) {
Lines 511-519 static GstPadProbeReturn webkitMediaStreamSrcPadProbeCb(GstPad* pad, GstPadProbe a/Source/WebCore/platform/mediastream/gstreamer/GStreamerMediaStreamSource.cpp_sec17
511
        gst_event_set_group_id(streamStart, 1);
542
        gst_event_set_group_id(streamStart, 1);
512
        gst_pad_push_event(pad, streamStart);
543
        gst_pad_push_event(pad, streamStart);
513
544
514
        webkitMediaStreamSrcAddPad(self, pad, data->padTemplate, WTFMove(data->tags), data->sourceType);
545
        webkitMediaStreamSrcAddPad(self, pad, data->padTemplate, WTFMove(data->tags), data->sourceType, data->padName);
515
        callOnMainThreadAndWait([element = data->element] {
546
        callOnMainThreadAndWait([element = data->element] {
516
            webkitMediaStreamSrcPostStreamCollection(WEBKIT_MEDIA_STREAM_SRC(element.get()));
547
            webkitMediaStreamSrcPostStreamCollection(WEBKIT_MEDIA_STREAM_SRC_CAST(element.get()));
517
        });
548
        });
518
        return GST_PAD_PROBE_REMOVE;
549
        return GST_PAD_PROBE_REMOVE;
519
    }
550
    }
Lines 524-623 static GstPadProbeReturn webkitMediaStreamSrcPadProbeCb(GstPad* pad, GstPadProbe a/Source/WebCore/platform/mediastream/gstreamer/GStreamerMediaStreamSource.cpp_sec18
524
    return GST_PAD_PROBE_OK;
555
    return GST_PAD_PROBE_OK;
525
}
556
}
526
557
527
static void webkitMediaStreamSrcSetupSrc(WebKitMediaStreamSrc* self, MediaStreamTrackPrivate* track, GstElement* element, GstStaticPadTemplate* padTemplate, bool onlyTrack)
558
void webkitMediaStreamSrcAddTrack(WebKitMediaStreamSrc* self, MediaStreamTrackPrivate* track, bool onlyTrack)
528
{
559
{
529
    const char* sourceType = "unknown";
560
    const char* sourceType = "unknown";
530
    switch (track->source().type()) {
561
    unsigned counter = 0;
531
    case RealtimeMediaSource::Type::Audio:
562
    GstStaticPadTemplate* padTemplate = nullptr;
563
    if (track->type() == RealtimeMediaSource::Type::Audio) {
564
        padTemplate = &audioSrcTemplate;
532
        sourceType = "audio";
565
        sourceType = "audio";
533
        break;
566
        counter = self->priv->audioPadCounter.exchangeAdd(1);
534
    case RealtimeMediaSource::Type::Video:
567
    } else if (track->type() == RealtimeMediaSource::Type::Video) {
568
        padTemplate = &videoSrcTemplate;
535
        sourceType = "video";
569
        sourceType = "video";
536
        break;
570
        counter = self->priv->videoPadCounter.exchangeAdd(1);
537
    case RealtimeMediaSource::Type::None:
571
    } else {
538
        ASSERT_NOT_REACHED();
572
        GST_WARNING_OBJECT(self, "Unsupported track type: %d", static_cast<int>(track->type()));
573
        return;
539
    }
574
    }
540
575
576
    StringBuilder padName;
577
    padName.append(sourceType);
578
    padName.append("_src");
579
    padName.append(counter);
580
541
    GST_DEBUG_OBJECT(self, "Setup %s source for track %s, only track: %s", sourceType, track->id().utf8().data(), boolForPrinting(onlyTrack));
581
    GST_DEBUG_OBJECT(self, "Setup %s source for track %s, only track: %s", sourceType, track->id().utf8().data(), boolForPrinting(onlyTrack));
582
583
    auto source = makeUnique<InternalSource>(GST_ELEMENT_CAST(self), *track, padName.toString());
584
    auto* element = source->get();
542
    gst_bin_add(GST_BIN_CAST(self), element);
585
    gst_bin_add(GST_BIN_CAST(self), element);
543
586
544
    auto pad = adoptGRef(gst_element_get_static_pad(element, "src"));
587
    auto pad = adoptGRef(gst_element_get_static_pad(element, "src"));
545
    auto tags = mediaStreamTrackPrivateGetTags(track);
588
    auto tags = mediaStreamTrackPrivateGetTags(track);
546
    if (!onlyTrack) {
589
    if (!onlyTrack) {
547
        auto* data = new ProbeData(GST_ELEMENT_CAST(self), padTemplate, WTFMove(tags), track->id().utf8().data(), track->source().type());
590
        auto* data = new ProbeData(GST_ELEMENT_CAST(self), padTemplate, WTFMove(tags), track->id().utf8().data(), track->source().type(), source->padName());
548
        gst_pad_add_probe(pad.get(), GST_PAD_PROBE_TYPE_EVENT_DOWNSTREAM, reinterpret_cast<GstPadProbeCallback>(webkitMediaStreamSrcPadProbeCb), data, [](gpointer data) {
591
        gst_pad_add_probe(pad.get(), GST_PAD_PROBE_TYPE_EVENT_DOWNSTREAM, reinterpret_cast<GstPadProbeCallback>(webkitMediaStreamSrcPadProbeCb), data, [](gpointer data) {
549
            delete reinterpret_cast<ProbeData*>(data);
592
            delete reinterpret_cast<ProbeData*>(data);
550
        });
593
        });
551
    } else {
594
    } else {
552
        gst_pad_set_active(pad.get(), TRUE);
595
        gst_pad_set_active(pad.get(), TRUE);
553
        webkitMediaStreamSrcAddPad(self, pad.get(), padTemplate, WTFMove(tags), track->source().type());
596
        webkitMediaStreamSrcAddPad(self, pad.get(), padTemplate, WTFMove(tags), track->source().type(), source->padName());
554
    }
597
    }
555
556
    auto* priv = self->priv;
557
    track->addObserver(*priv->mediaStreamTrackObserver);
558
    switch (track->type()) {
559
    case RealtimeMediaSource::Type::Audio:
560
        track->source().addAudioSampleObserver(*priv->mediaStreamTrackObserver);
561
        break;
562
    case RealtimeMediaSource::Type::Video:
563
        track->source().addVideoSampleObserver(*priv->mediaStreamTrackObserver);
564
        break;
565
    case RealtimeMediaSource::Type::None:
566
        ASSERT_NOT_REACHED();
567
    }
568
569
    gst_element_sync_state_with_parent(element);
598
    gst_element_sync_state_with_parent(element);
570
}
571
572
static void webkitMediaStreamSrcPostStreamCollection(WebKitMediaStreamSrc* self)
573
{
574
    auto* priv = self->priv;
575
576
    GST_OBJECT_LOCK(self);
577
    if (priv->stream && (!priv->stream->active() || !priv->stream->hasTracks())) {
578
        GST_OBJECT_UNLOCK(self);
579
        return;
580
    }
581
582
    auto upstreamId = priv->stream ? priv->stream->id() : createCanonicalUUIDString();
583
    priv->streamCollection = adoptGRef(gst_stream_collection_new(upstreamId.ascii().data()));
584
    for (auto& track : priv->tracks) {
585
        if (!track->isActive())
586
            continue;
587
        gst_stream_collection_add_stream(priv->streamCollection.get(), webkitMediaStreamNew(track.get()));
588
    }
589
590
    GST_OBJECT_UNLOCK(self);
591
592
    GST_DEBUG_OBJECT(self, "Posting stream collection");
593
    gst_element_post_message(GST_ELEMENT_CAST(self), gst_message_new_stream_collection(GST_OBJECT_CAST(self), priv->streamCollection.get()));
594
}
595
596
void webkitMediaStreamSrcAddTrack(WebKitMediaStreamSrc* self, MediaStreamTrackPrivate* track, bool onlyTrack)
597
{
598
    auto* priv = self->priv;
599
    if (track->type() == RealtimeMediaSource::Type::Audio) {
600
        priv->audioSrc.emplace(track->isCaptureTrack(), track->type());
601
        webkitMediaStreamSrcSetupSrc(self, track, priv->audioSrc->get(), &audioSrcTemplate, onlyTrack);
602
    } else if (track->type() == RealtimeMediaSource::Type::Video) {
603
        priv->videoSrc.emplace(track->isCaptureTrack(), track->type());
604
        webkitMediaStreamSrcSetupSrc(self, track, priv->videoSrc->get(), &videoSrcTemplate, onlyTrack);
605
    } else
606
        GST_INFO_OBJECT(self, "Unsupported track type: %d", static_cast<int>(track->type()));
607
599
600
    source->startObserving();
601
    self->priv->sources.append(WTFMove(source));
608
    self->priv->tracks.append(track);
602
    self->priv->tracks.append(track);
609
}
603
}
610
604
611
static void webkitMediaStreamSrcRemoveTrackByType(WebKitMediaStreamSrc* self, RealtimeMediaSource::Type trackType)
612
{
613
    if (trackType == RealtimeMediaSource::Type::Audio)
614
        self->priv->audioSrc.reset();
615
    else if (trackType == RealtimeMediaSource::Type::Video)
616
        self->priv->videoSrc.reset();
617
    else
618
        GST_INFO_OBJECT(self, "Unsupported track type: %d", static_cast<int>(trackType));
619
}
620
621
void webkitMediaStreamSrcSetStream(WebKitMediaStreamSrc* self, MediaStreamPrivate* stream, bool isVideoPlayer)
605
void webkitMediaStreamSrcSetStream(WebKitMediaStreamSrc* self, MediaStreamPrivate* stream, bool isVideoPlayer)
622
{
606
{
623
    ASSERT(WEBKIT_IS_MEDIA_STREAM_SRC(self));
607
    ASSERT(WEBKIT_IS_MEDIA_STREAM_SRC(self));
Lines 633-657 void webkitMediaStreamSrcSetStream(WebKitMediaStreamSrc* self, MediaStreamPrivat a/Source/WebCore/platform/mediastream/gstreamer/GStreamerMediaStreamSource.cpp_sec19
633
        webkitMediaStreamSrcAddTrack(self, track.get(), onlyTrack);
617
        webkitMediaStreamSrcAddTrack(self, track.get(), onlyTrack);
634
    }
618
    }
635
    webkitMediaStreamSrcPostStreamCollection(self);
619
    webkitMediaStreamSrcPostStreamCollection(self);
636
    gst_element_no_more_pads(GST_ELEMENT_CAST(self));
637
}
638
639
static void webkitMediaStreamSrcPushVideoSample(WebKitMediaStreamSrc* self, GstSample* sample)
640
{
641
    if (self->priv->videoSrc)
642
        self->priv->videoSrc->pushSample(sample);
643
}
644
645
static void webkitMediaStreamSrcPushAudioSample(WebKitMediaStreamSrc* self, const GRefPtr<GstSample>& sample)
646
{
647
    if (self->priv->audioSrc)
648
        self->priv->audioSrc->pushSample(sample.get());
649
}
620
}
650
621
651
static void webkitMediaStreamSrcTrackEnded(WebKitMediaStreamSrc* self, MediaStreamTrackPrivate& track)
622
static void webkitMediaStreamSrcTrackEnded(WebKitMediaStreamSrc* self, InternalSource& source)
652
{
623
{
653
    GRefPtr<GstPad> pad;
624
    GRefPtr<GstPad> pad;
654
625
626
    auto& track = source.track();
655
    GST_DEBUG_OBJECT(self, "Track %s ended", track.label().utf8().data());
627
    GST_DEBUG_OBJECT(self, "Track %s ended", track.label().utf8().data());
656
    GST_OBJECT_LOCK(self);
628
    GST_OBJECT_LOCK(self);
657
    GstElement* element = GST_ELEMENT_CAST(self);
629
    GstElement* element = GST_ELEMENT_CAST(self);
Lines 684-689 static void webkitMediaStreamSrcTrackEnded(WebKitMediaStreamSrc* self, MediaStre a/Source/WebCore/platform/mediastream/gstreamer/GStreamerMediaStreamSource.cpp_sec20
684
    auto tags = mediaStreamTrackPrivateGetTags(&track);
656
    auto tags = mediaStreamTrackPrivateGetTags(&track);
685
    gst_pad_push_event(pad.get(), gst_event_new_tag(tags.leakRef()));
657
    gst_pad_push_event(pad.get(), gst_event_new_tag(tags.leakRef()));
686
    gst_pad_push_event(pad.get(), gst_event_new_eos());
658
    gst_pad_push_event(pad.get(), gst_event_new_eos());
659
660
    GST_STATE_LOCK(GST_ELEMENT_CAST(self));
661
    gst_element_set_locked_state(source.get(), true);
662
    gst_element_set_state(source.get(), GST_STATE_NULL);
663
    gst_bin_remove(GST_BIN_CAST(self), source.get());
664
    gst_element_set_locked_state(source.get(), false);
665
    GST_STATE_UNLOCK(GST_ELEMENT_CAST(self));
666
667
    auto proxyPad = adoptGRef(GST_PAD_CAST(gst_proxy_pad_get_internal(GST_PROXY_PAD(pad.get()))));
668
    if (proxyPad)
669
        gst_flow_combiner_remove_pad(self->priv->flowCombiner.get(), proxyPad.get());
670
671
    gst_pad_set_active(pad.get(), FALSE);
672
    gst_element_remove_pad(GST_ELEMENT_CAST(self), pad.get());
673
}
674
675
void InternalSource::trackEnded(MediaStreamTrackPrivate&)
676
{
677
    if (m_parent)
678
        webkitMediaStreamSrcTrackEnded(WEBKIT_MEDIA_STREAM_SRC_CAST(m_parent), *this);
679
680
    stopObserving();
681
}
682
683
void webkitMediaStreamSrcConfigureAudioTracks(WebKitMediaStreamSrc* self, float volume, bool isMuted, bool isPlaying)
684
{
685
    for (auto& source : self->priv->sources) {
686
        if (source->track().type() == RealtimeMediaSource::Type::Audio)
687
            source->configureAudioTrack(volume, isMuted, isPlaying);
688
    }
687
}
689
}
688
690
689
GstElement* webkitMediaStreamSrcNew()
691
GstElement* webkitMediaStreamSrcNew()
- a/Source/WebCore/platform/mediastream/gstreamer/GStreamerMediaStreamSource.h -5 / +4 lines
Lines 30-43 class MediaStreamPrivate; a/Source/WebCore/platform/mediastream/gstreamer/GStreamerMediaStreamSource.h_sec1
30
class MediaStreamTrackPrivate;
30
class MediaStreamTrackPrivate;
31
}
31
}
32
32
33
G_BEGIN_DECLS
34
35
#define WEBKIT_MEDIA_TRACK_TAG_WIDTH "webkit-media-stream-width"
33
#define WEBKIT_MEDIA_TRACK_TAG_WIDTH "webkit-media-stream-width"
36
#define WEBKIT_MEDIA_TRACK_TAG_HEIGHT "webkit-media-stream-height"
34
#define WEBKIT_MEDIA_TRACK_TAG_HEIGHT "webkit-media-stream-height"
37
#define WEBKIT_MEDIA_TRACK_TAG_KIND "webkit-media-stream-kind"
35
#define WEBKIT_MEDIA_TRACK_TAG_KIND "webkit-media-stream-kind"
38
36
39
#define WEBKIT_MEDIA_STREAM_SRC(o) (G_TYPE_CHECK_INSTANCE_CAST((o), WEBKIT_TYPE_MEDIA_STREAM_SRC, WebKitMediaStreamSrc))
37
#define WEBKIT_MEDIA_STREAM_SRC(o) (G_TYPE_CHECK_INSTANCE_CAST((o), WEBKIT_TYPE_MEDIA_STREAM_SRC, WebKitMediaStreamSrc))
40
#define WEBKIT_IS_MEDIA_STREAM_SRC(o) (G_TYPE_CHECK_INSTANCE_TYPE((o), WEBKIT_TYPE_MEDIA_STREAM_SRC))
38
#define WEBKIT_IS_MEDIA_STREAM_SRC(o) (G_TYPE_CHECK_INSTANCE_TYPE((o), WEBKIT_TYPE_MEDIA_STREAM_SRC))
39
#define WEBKIT_MEDIA_STREAM_SRC_CAST(o) ((WebKitMediaStreamSrc*) o)
40
41
#define WEBKIT_TYPE_MEDIA_STREAM_SRC (webkit_media_stream_src_get_type())
41
#define WEBKIT_TYPE_MEDIA_STREAM_SRC (webkit_media_stream_src_get_type())
42
GType webkit_media_stream_src_get_type(void);
42
GType webkit_media_stream_src_get_type(void);
43
43
Lines 54-63 struct _WebKitMediaStreamSrcClass { a/Source/WebCore/platform/mediastream/gstreamer/GStreamerMediaStreamSource.h_sec2
54
    GstBinClass parentClass;
54
    GstBinClass parentClass;
55
};
55
};
56
56
57
GstElement* webkitMediaStreamSrcNew();
57
void webkitMediaStreamSrcSetStream(WebKitMediaStreamSrc*, WebCore::MediaStreamPrivate*, bool isVideoPlayer);
58
void webkitMediaStreamSrcSetStream(WebKitMediaStreamSrc*, WebCore::MediaStreamPrivate*, bool isVideoPlayer);
58
void webkitMediaStreamSrcAddTrack(WebKitMediaStreamSrc*, WebCore::MediaStreamTrackPrivate*, bool onlyTrack);
59
void webkitMediaStreamSrcAddTrack(WebKitMediaStreamSrc*, WebCore::MediaStreamTrackPrivate*, bool onlyTrack);
59
GstElement* webkitMediaStreamSrcNew();
60
void webkitMediaStreamSrcConfigureAudioTracks(WebKitMediaStreamSrc*, float volume, bool isMuted, bool isPlaying);
60
61
G_END_DECLS
62
61
63
#endif // ENABLE(VIDEO) && ENABLE(MEDIA_STREAM) && USE(GSTREAMER)
62
#endif // ENABLE(VIDEO) && ENABLE(MEDIA_STREAM) && USE(GSTREAMER)
- a/LayoutTests/ChangeLog +13 lines
Lines 1-3 a/LayoutTests/ChangeLog_sec1
1
2021-04-17  Philippe Normand  <pnormand@igalia.com>
2
3
        [GStreamer][MediaStream] fast/mediastream/play-newly-added-audio-track.html is failing since added in r260380
4
        https://bugs.webkit.org/show_bug.cgi?id=210840
5
6
        Reviewed by Xabier Rodriguez-Calvar.
7
8
        Remove flakyness from test and unflag it on GTK.
9
10
        * fast/mediastream/play-newly-added-audio-track.html: addTrack might have asynchronous
11
        effects, so it's better to rely on the trackadded event when testing its result.
12
        * platform/gtk/TestExpectations:
13
1
2021-04-16  Diego Pino Garcia  <dpino@igalia.com>
14
2021-04-16  Diego Pino Garcia  <dpino@igalia.com>
2
15
3
        [GLIB] Unreviewed test gardening. Update focus-visible tests baselines after r276127.
16
        [GLIB] Unreviewed test gardening. Update focus-visible tests baselines after r276127.
- a/LayoutTests/fast/mediastream/play-newly-added-audio-track.html -5 / +11 lines
Lines 11-21 promise_test(async () => { a/LayoutTests/fast/mediastream/play-newly-added-audio-track.html_sec1
11
11
12
    await video.play();
12
    await video.play();
13
13
14
    video.srcObject.ontrackadded = () => {
15
        if (window.internals)
16
            assert_true(internals.shouldAudioTrackPlay(video.audioTracks[0]));
17
    };
18
14
    let stream2 = await navigator.mediaDevices.getUserMedia({ audio : true });
19
    let stream2 = await navigator.mediaDevices.getUserMedia({ audio : true });
15
    video.srcObject.addTrack(stream2.getAudioTracks()[0]);
20
    video.srcObject.addTrack(stream2.getAudioTracks()[0]);
16
21
    await new Promise(resolve => setTimeout(resolve, 50));
17
    if (window.internals)
18
        assert_true(internals.shouldAudioTrackPlay(video.audioTracks[0]));
19
}, "Add an audio track while playing video");
22
}, "Add an audio track while playing video");
20
23
21
promise_test(async () => {
24
promise_test(async () => {
Lines 24-33 promise_test(async () => { a/LayoutTests/fast/mediastream/play-newly-added-audio-track.html_sec2
24
27
25
    await video.play();
28
    await video.play();
26
29
30
    video.srcObject.ontrackadded = () => {
31
        if (window.internals)
32
            assert_true(internals.shouldAudioTrackPlay(video.audioTracks[1]));
33
    };
34
27
    video.srcObject.addTrack(stream.getAudioTracks()[0]);
35
    video.srcObject.addTrack(stream.getAudioTracks()[0]);
28
    await new Promise(resolve => setTimeout(resolve, 50));
36
    await new Promise(resolve => setTimeout(resolve, 50));
29
    if (window.internals)
30
        assert_true(internals.shouldAudioTrackPlay(video.audioTracks[1]));
31
}, "Add an audio track while playing audio");
37
}, "Add an audio track while playing audio");
32
        </script>
38
        </script>
33
    </head>
39
    </head>
- a/LayoutTests/platform/gtk/TestExpectations -1 lines
Lines 161-167 webkit.org/b/219530 fast/dom/HTMLAnchorElement/anchor-nodownload-set.html [ Fail a/LayoutTests/platform/gtk/TestExpectations_sec1
161
161
162
# GStreamer - GTK-Only. Most GStreamer issues probably will go in the glib expectations
162
# GStreamer - GTK-Only. Most GStreamer issues probably will go in the glib expectations
163
webkit.org/b/210528 media/video-supports-fullscreen.html [ Pass ]
163
webkit.org/b/210528 media/video-supports-fullscreen.html [ Pass ]
164
webkit.org/b/210840 fast/mediastream/play-newly-added-audio-track.html [ Failure ]
165
164
166
# Layout
165
# Layout
167
webkit.org/b/218510 fast/dom/Range/getBoundingClientRect.html [ Failure ]
166
webkit.org/b/218510 fast/dom/Range/getBoundingClientRect.html [ Failure ]

Return to Bug 210840