[GStreamer] media/video-src-blob-using-open-panel.html crashes in Debug
[WebKit-https.git] / Source / WebCore / platform / graphics / gstreamer / WebKitWebSourceGStreamer.cpp
1 /*
2  *  Copyright (C) 2009, 2010 Sebastian Dröge <sebastian.droege@collabora.co.uk>
3  *  Copyright (C) 2013 Collabora Ltd.
4  *
5  *  This library is free software; you can redistribute it and/or
6  *  modify it under the terms of the GNU Lesser General Public
7  *  License as published by the Free Software Foundation; either
8  *  version 2 of the License, or (at your option) any later version.
9  *
10  *  This library is distributed in the hope that it will be useful,
11  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
12  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  *  Lesser General Public License for more details.
14  *
15  *  You should have received a copy of the GNU Lesser General Public
16  *  License along with this library; if not, write to the Free Software
17  *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
18  */
19
20 #include "config.h"
21 #include "WebKitWebSourceGStreamer.h"
22
23 #if ENABLE(VIDEO) && USE(GSTREAMER)
24
25 #include "GRefPtrGStreamer.h"
26 #include "GStreamerUtilities.h"
27 #include "GUniquePtrGStreamer.h"
28 #include "HTTPHeaderNames.h"
29 #include "MainThreadNotifier.h"
30 #include "MediaPlayer.h"
31 #include "PlatformMediaResourceLoader.h"
32 #include "ResourceError.h"
33 #include "ResourceRequest.h"
34 #include "ResourceResponse.h"
35 #include <cstdint>
36 #include <gst/app/gstappsrc.h>
37 #include <gst/pbutils/missing-plugins.h>
38 #include <wtf/text/CString.h>
39
40 using namespace WebCore;
41
42 class CachedResourceStreamingClient final : public PlatformMediaResourceClient {
43     WTF_MAKE_NONCOPYABLE(CachedResourceStreamingClient);
44 public:
45     CachedResourceStreamingClient(WebKitWebSrc*, ResourceRequest&&);
46     virtual ~CachedResourceStreamingClient();
47 private:
48     // PlatformMediaResourceClient virtual methods.
49     void responseReceived(PlatformMediaResource&, const ResourceResponse&) override;
50     void dataReceived(PlatformMediaResource&, const char*, int) override;
51     void accessControlCheckFailed(PlatformMediaResource&, const ResourceError&) override;
52     void loadFailed(PlatformMediaResource&, const ResourceError&) override;
53     void loadFinished(PlatformMediaResource&) override;
54
55     GRefPtr<GstElement> m_src;
56     ResourceRequest m_request;
57 };
58
59 enum MainThreadSourceNotification {
60     Start = 1 << 0,
61     Stop = 1 << 1,
62     NeedData = 1 << 2,
63     EnoughData = 1 << 3,
64     Seek = 1 << 4
65 };
66
67 #define WEBKIT_WEB_SRC_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE((obj), WEBKIT_TYPE_WEB_SRC, WebKitWebSrcPrivate))
68 struct _WebKitWebSrcPrivate {
69     GstAppSrc* appsrc;
70     GstPad* srcpad;
71     CString originalURI;
72     CString redirectedURI;
73     bool keepAlive;
74     GUniquePtr<GstStructure> extraHeaders;
75     bool compress;
76     GUniquePtr<gchar> httpMethod;
77
78     WebCore::MediaPlayer* player;
79
80     RefPtr<PlatformMediaResourceLoader> loader;
81     RefPtr<PlatformMediaResource> resource;
82
83     bool didPassAccessControlCheck;
84
85     guint64 offset;
86     guint64 size;
87     gboolean seekable;
88     bool paused;
89     bool isSeeking;
90
91     guint64 requestedOffset;
92
93     RefPtr<MainThreadNotifier<MainThreadSourceNotification>> notifier;
94     GRefPtr<GstBuffer> buffer;
95 };
96
97 enum {
98     PROP_0,
99     PROP_LOCATION,
100     PROP_RESOLVED_LOCATION,
101     PROP_KEEP_ALIVE,
102     PROP_EXTRA_HEADERS,
103     PROP_COMPRESS,
104     PROP_METHOD
105 };
106
107 static GstStaticPadTemplate srcTemplate = GST_STATIC_PAD_TEMPLATE("src",
108                                                                   GST_PAD_SRC,
109                                                                   GST_PAD_ALWAYS,
110                                                                   GST_STATIC_CAPS_ANY);
111
112 GST_DEBUG_CATEGORY_STATIC(webkit_web_src_debug);
113 #define GST_CAT_DEFAULT webkit_web_src_debug
114
115 static void webKitWebSrcUriHandlerInit(gpointer gIface, gpointer ifaceData);
116
117 static void webKitWebSrcDispose(GObject*);
118 static void webKitWebSrcFinalize(GObject*);
119 static void webKitWebSrcSetProperty(GObject*, guint propertyID, const GValue*, GParamSpec*);
120 static void webKitWebSrcGetProperty(GObject*, guint propertyID, GValue*, GParamSpec*);
121 static GstStateChangeReturn webKitWebSrcChangeState(GstElement*, GstStateChange);
122
123 static gboolean webKitWebSrcQueryWithParent(GstPad*, GstObject*, GstQuery*);
124
125 static void webKitWebSrcNeedData(WebKitWebSrc*);
126 static void webKitWebSrcEnoughData(WebKitWebSrc*);
127 static gboolean webKitWebSrcSeek(WebKitWebSrc*, guint64);
128
129 static GstAppSrcCallbacks appsrcCallbacks = {
130     // need_data
131     [](GstAppSrc*, guint, gpointer userData) {
132         webKitWebSrcNeedData(WEBKIT_WEB_SRC(userData));
133     },
134     // enough_data
135     [](GstAppSrc*, gpointer userData) {
136         webKitWebSrcEnoughData(WEBKIT_WEB_SRC(userData));
137     },
138     // seek_data
139     [](GstAppSrc*, guint64 offset, gpointer userData) -> gboolean {
140         return webKitWebSrcSeek(WEBKIT_WEB_SRC(userData), offset);
141     },
142     { nullptr }
143 };
144
145 #define webkit_web_src_parent_class parent_class
146 // We split this out into another macro to avoid a check-webkit-style error.
147 #define WEBKIT_WEB_SRC_CATEGORY_INIT GST_DEBUG_CATEGORY_INIT(webkit_web_src_debug, "webkitwebsrc", 0, "websrc element");
148 G_DEFINE_TYPE_WITH_CODE(WebKitWebSrc, webkit_web_src, GST_TYPE_BIN,
149                          G_IMPLEMENT_INTERFACE(GST_TYPE_URI_HANDLER, webKitWebSrcUriHandlerInit);
150                          WEBKIT_WEB_SRC_CATEGORY_INIT);
151
152 static void webkit_web_src_class_init(WebKitWebSrcClass* klass)
153 {
154     GObjectClass* oklass = G_OBJECT_CLASS(klass);
155     GstElementClass* eklass = GST_ELEMENT_CLASS(klass);
156
157     oklass->dispose = webKitWebSrcDispose;
158     oklass->finalize = webKitWebSrcFinalize;
159     oklass->set_property = webKitWebSrcSetProperty;
160     oklass->get_property = webKitWebSrcGetProperty;
161
162     gst_element_class_add_pad_template(eklass,
163                                        gst_static_pad_template_get(&srcTemplate));
164     gst_element_class_set_metadata(eklass, "WebKit Web source element", "Source", "Handles HTTP/HTTPS uris",
165                                "Sebastian Dröge <sebastian.droege@collabora.co.uk>");
166
167     /* Allows setting the uri using the 'location' property, which is used
168      * for example by gst_element_make_from_uri() */
169     g_object_class_install_property(oklass, PROP_LOCATION,
170         g_param_spec_string("location", "location", "Location to read from",
171             nullptr, static_cast<GParamFlags>(G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)));
172
173     g_object_class_install_property(oklass, PROP_RESOLVED_LOCATION,
174         g_param_spec_string("resolved-location", "Resolved location", "The location resolved by the server",
175             nullptr, static_cast<GParamFlags>(G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)));
176
177     g_object_class_install_property(oklass, PROP_KEEP_ALIVE,
178         g_param_spec_boolean("keep-alive", "keep-alive", "Use HTTP persistent connections",
179             FALSE, static_cast<GParamFlags>(G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)));
180
181     g_object_class_install_property(oklass, PROP_EXTRA_HEADERS,
182         g_param_spec_boxed("extra-headers", "Extra Headers", "Extra headers to append to the HTTP request",
183             GST_TYPE_STRUCTURE, static_cast<GParamFlags>(G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)));
184
185     g_object_class_install_property(oklass, PROP_COMPRESS,
186         g_param_spec_boolean("compress", "Compress", "Allow compressed content encodings",
187             FALSE, static_cast<GParamFlags>(G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)));
188
189     g_object_class_install_property(oklass, PROP_METHOD,
190         g_param_spec_string("method", "method", "The HTTP method to use (default: GET)",
191             nullptr, static_cast<GParamFlags>(G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)));
192
193     eklass->change_state = webKitWebSrcChangeState;
194
195     g_type_class_add_private(klass, sizeof(WebKitWebSrcPrivate));
196 }
197
198 static void webkit_web_src_init(WebKitWebSrc* src)
199 {
200     WebKitWebSrcPrivate* priv = WEBKIT_WEB_SRC_GET_PRIVATE(src);
201
202     src->priv = priv;
203     new (priv) WebKitWebSrcPrivate();
204
205     priv->notifier = MainThreadNotifier<MainThreadSourceNotification>::create();
206
207     priv->appsrc = GST_APP_SRC(gst_element_factory_make("appsrc", nullptr));
208     if (!priv->appsrc) {
209         GST_ERROR_OBJECT(src, "Failed to create appsrc");
210         return;
211     }
212
213     gst_bin_add(GST_BIN(src), GST_ELEMENT(priv->appsrc));
214
215     GRefPtr<GstPad> targetPad = adoptGRef(gst_element_get_static_pad(GST_ELEMENT(priv->appsrc), "src"));
216     priv->srcpad = webkitGstGhostPadFromStaticTemplate(&srcTemplate, "src", targetPad.get());
217
218     gst_element_add_pad(GST_ELEMENT(src), priv->srcpad);
219
220     GST_OBJECT_FLAG_SET(priv->srcpad, GST_PAD_FLAG_NEED_PARENT);
221     gst_pad_set_query_function(priv->srcpad, webKitWebSrcQueryWithParent);
222
223     gst_app_src_set_callbacks(priv->appsrc, &appsrcCallbacks, src, nullptr);
224     gst_app_src_set_emit_signals(priv->appsrc, FALSE);
225     gst_app_src_set_stream_type(priv->appsrc, GST_APP_STREAM_TYPE_SEEKABLE);
226
227     // 512k is a abitrary number but we should choose a value
228     // here to not pause/unpause the SoupMessage too often and
229     // to make sure there's always some data available for
230     // GStreamer to handle.
231     gst_app_src_set_max_bytes(priv->appsrc, 512 * 1024);
232
233     // Emit the need-data signal if the queue contains less
234     // than 20% of data. Without this the need-data signal
235     // is emitted when the queue is empty, we then dispatch
236     // the soup message unpausing to the main loop and from
237     // there unpause the soup message. This already takes
238     // quite some time and libsoup even needs some more time
239     // to actually provide data again. If we do all this
240     // already if the queue is 20% empty, it's much more
241     // likely that libsoup already provides new data before
242     // the queue is really empty.
243     // This might need tweaking for ports not using libsoup.
244     g_object_set(priv->appsrc, "min-percent", 20, nullptr);
245
246     gst_base_src_set_automatic_eos(GST_BASE_SRC(priv->appsrc), FALSE);
247
248     gst_app_src_set_caps(priv->appsrc, nullptr);
249     gst_app_src_set_size(priv->appsrc, -1);
250 }
251
252 static void webKitWebSrcDispose(GObject* object)
253 {
254     WebKitWebSrcPrivate* priv = WEBKIT_WEB_SRC(object)->priv;
255     if (priv->notifier) {
256         priv->notifier->invalidate();
257         priv->notifier = nullptr;
258     }
259
260     GST_CALL_PARENT(G_OBJECT_CLASS, dispose, (object));
261 }
262
263 static void webKitWebSrcFinalize(GObject* object)
264 {
265     WebKitWebSrcPrivate* priv = WEBKIT_WEB_SRC(object)->priv;
266
267     priv->~WebKitWebSrcPrivate();
268
269     GST_CALL_PARENT(G_OBJECT_CLASS, finalize, (object));
270 }
271
272 static void webKitWebSrcSetProperty(GObject* object, guint propID, const GValue* value, GParamSpec* pspec)
273 {
274     WebKitWebSrc* src = WEBKIT_WEB_SRC(object);
275
276     switch (propID) {
277     case PROP_LOCATION:
278         gst_uri_handler_set_uri(reinterpret_cast<GstURIHandler*>(src), g_value_get_string(value), nullptr);
279         break;
280     case PROP_KEEP_ALIVE:
281         src->priv->keepAlive = g_value_get_boolean(value);
282         break;
283     case PROP_EXTRA_HEADERS: {
284         const GstStructure* s = gst_value_get_structure(value);
285         src->priv->extraHeaders.reset(s ? gst_structure_copy(s) : nullptr);
286         break;
287     }
288     case PROP_COMPRESS:
289         src->priv->compress = g_value_get_boolean(value);
290         break;
291     case PROP_METHOD:
292         src->priv->httpMethod.reset(g_value_dup_string(value));
293         break;
294     default:
295         G_OBJECT_WARN_INVALID_PROPERTY_ID(object, propID, pspec);
296         break;
297     }
298 }
299
300 static void webKitWebSrcGetProperty(GObject* object, guint propID, GValue* value, GParamSpec* pspec)
301 {
302     WebKitWebSrc* src = WEBKIT_WEB_SRC(object);
303     WebKitWebSrcPrivate* priv = src->priv;
304
305     switch (propID) {
306     case PROP_LOCATION:
307         g_value_set_string(value, priv->originalURI.data());
308         break;
309     case PROP_RESOLVED_LOCATION:
310         g_value_set_string(value, priv->redirectedURI.isNull() ? priv->originalURI.data() : priv->redirectedURI.data());
311         break;
312     case PROP_KEEP_ALIVE:
313         g_value_set_boolean(value, priv->keepAlive);
314         break;
315     case PROP_EXTRA_HEADERS:
316         gst_value_set_structure(value, priv->extraHeaders.get());
317         break;
318     case PROP_COMPRESS:
319         g_value_set_boolean(value, priv->compress);
320         break;
321     case PROP_METHOD:
322         g_value_set_string(value, priv->httpMethod.get());
323         break;
324     default:
325         G_OBJECT_WARN_INVALID_PROPERTY_ID(object, propID, pspec);
326         break;
327     }
328 }
329
330 static void webKitWebSrcStop(WebKitWebSrc* src)
331 {
332     WebKitWebSrcPrivate* priv = src->priv;
333
334     if (priv->resource || (priv->loader && !priv->keepAlive)) {
335         GRefPtr<WebKitWebSrc> protector = WTF::ensureGRef(src);
336         priv->notifier->cancelPendingNotifications(MainThreadSourceNotification::NeedData | MainThreadSourceNotification::EnoughData | MainThreadSourceNotification::Seek);
337         priv->notifier->notify(MainThreadSourceNotification::Stop, [protector, keepAlive = priv->keepAlive] {
338             WebKitWebSrcPrivate* priv = protector->priv;
339
340             if (priv->resource) {
341                 priv->resource->stop();
342                 priv->resource->setClient(nullptr);
343                 priv->resource = nullptr;
344             }
345
346             if (!keepAlive)
347                 priv->loader = nullptr;
348         });
349     }
350
351     bool wasSeeking = std::exchange(priv->isSeeking, false);
352
353     if (priv->buffer) {
354         unmapGstBuffer(priv->buffer.get());
355         priv->buffer.clear();
356     }
357
358     priv->paused = false;
359
360     priv->offset = 0;
361
362     if (!wasSeeking) {
363         priv->size = 0;
364         priv->requestedOffset = 0;
365         priv->player = nullptr;
366         priv->seekable = FALSE;
367     }
368
369     if (priv->appsrc) {
370         gst_app_src_set_caps(priv->appsrc, nullptr);
371         if (!wasSeeking)
372             gst_app_src_set_size(priv->appsrc, -1);
373     }
374
375     GST_DEBUG_OBJECT(src, "Stopped request");
376 }
377
378 static bool webKitWebSrcSetExtraHeader(GQuark fieldId, const GValue* value, gpointer userData)
379 {
380     GUniquePtr<gchar> fieldContent;
381
382     if (G_VALUE_HOLDS_STRING(value))
383         fieldContent.reset(g_value_dup_string(value));
384     else {
385         GValue dest = G_VALUE_INIT;
386
387         g_value_init(&dest, G_TYPE_STRING);
388         if (g_value_transform(value, &dest))
389             fieldContent.reset(g_value_dup_string(&dest));
390     }
391
392     const gchar* fieldName = g_quark_to_string(fieldId);
393     if (!fieldContent.get()) {
394         GST_ERROR("extra-headers field '%s' contains no value or can't be converted to a string", fieldName);
395         return false;
396     }
397
398     GST_DEBUG("Appending extra header: \"%s: %s\"", fieldName, fieldContent.get());
399     ResourceRequest* request = static_cast<ResourceRequest*>(userData);
400     request->setHTTPHeaderField(fieldName, fieldContent.get());
401     return true;
402 }
403
404 static gboolean webKitWebSrcProcessExtraHeaders(GQuark fieldId, const GValue* value, gpointer userData)
405 {
406     if (G_VALUE_TYPE(value) == GST_TYPE_ARRAY) {
407         unsigned size = gst_value_array_get_size(value);
408
409         for (unsigned i = 0; i < size; i++) {
410             if (!webKitWebSrcSetExtraHeader(fieldId, gst_value_array_get_value(value, i), userData))
411                 return FALSE;
412         }
413         return TRUE;
414     }
415
416     if (G_VALUE_TYPE(value) == GST_TYPE_LIST) {
417         unsigned size = gst_value_list_get_size(value);
418
419         for (unsigned i = 0; i < size; i++) {
420             if (!webKitWebSrcSetExtraHeader(fieldId, gst_value_list_get_value(value, i), userData))
421                 return FALSE;
422         }
423         return TRUE;
424     }
425
426     return webKitWebSrcSetExtraHeader(fieldId, value, userData);
427 }
428
429 static void webKitWebSrcStart(WebKitWebSrc* src)
430 {
431     WebKitWebSrcPrivate* priv = src->priv;
432     ASSERT(priv->player);
433
434     priv->didPassAccessControlCheck = false;
435
436     if (priv->originalURI.isNull()) {
437         GST_ERROR_OBJECT(src, "No URI provided");
438         webKitWebSrcStop(src);
439         return;
440     }
441
442     GST_DEBUG_OBJECT(src, "Fetching %s", priv->originalURI.data());
443     URL url = URL(URL(), priv->originalURI.data());
444
445     ResourceRequest request(url);
446     request.setAllowCookies(true);
447     request.setFirstPartyForCookies(url);
448
449     priv->size = 0;
450
451     request.setHTTPReferrer(priv->player->referrer());
452
453     if (priv->httpMethod.get())
454         request.setHTTPMethod(priv->httpMethod.get());
455
456 #if USE(SOUP)
457     // By default, HTTP Accept-Encoding is disabled here as we don't
458     // want the received response to be encoded in any way as we need
459     // to rely on the proper size of the returned data on
460     // didReceiveResponse.
461     // If Accept-Encoding is used, the server may send the data in encoded format and
462     // request.expectedContentLength() will have the "wrong" size (the size of the
463     // compressed data), even though the data received in didReceiveData is uncompressed.
464     // This is however useful to enable for adaptive streaming
465     // scenarios, when the demuxer needs to download playlists.
466     if (!priv->compress)
467         request.setAcceptEncoding(false);
468 #endif
469
470     // Let Apple web servers know we want to access their nice movie trailers.
471     if (!g_ascii_strcasecmp("movies.apple.com", url.host().utf8().data())
472         || !g_ascii_strcasecmp("trailers.apple.com", url.host().utf8().data()))
473         request.setHTTPUserAgent("Quicktime/7.6.6");
474
475     if (priv->requestedOffset) {
476         GUniquePtr<gchar> val(g_strdup_printf("bytes=%" G_GUINT64_FORMAT "-", priv->requestedOffset));
477         request.setHTTPHeaderField(HTTPHeaderName::Range, val.get());
478     }
479     priv->offset = priv->requestedOffset;
480
481     GST_DEBUG_OBJECT(src, "Persistent connection support %s", priv->keepAlive ? "enabled" : "disabled");
482     if (!priv->keepAlive) {
483         request.setHTTPHeaderField(HTTPHeaderName::Connection, "close");
484     }
485
486     if (priv->extraHeaders)
487         gst_structure_foreach(priv->extraHeaders.get(), webKitWebSrcProcessExtraHeaders, &request);
488
489     // We always request Icecast/Shoutcast metadata, just in case ...
490     request.setHTTPHeaderField(HTTPHeaderName::IcyMetadata, "1");
491
492     GRefPtr<WebKitWebSrc> protector = WTF::ensureGRef(src);
493     priv->notifier->notify(MainThreadSourceNotification::Start, [protector, request = WTFMove(request)] {
494         WebKitWebSrcPrivate* priv = protector->priv;
495
496         if (!priv->loader)
497             priv->loader = priv->player->createResourceLoader();
498
499         PlatformMediaResourceLoader::LoadOptions loadOptions = 0;
500         if (request.url().protocolIsBlob())
501             loadOptions |= PlatformMediaResourceLoader::LoadOption::BufferData;
502         priv->resource = priv->loader->requestResource(ResourceRequest(request), loadOptions);
503         if (priv->resource) {
504             priv->resource->setClient(std::make_unique<CachedResourceStreamingClient>(protector.get(), ResourceRequest(request)));
505             GST_DEBUG_OBJECT(protector.get(), "Started request");
506         } else {
507             GST_ERROR_OBJECT(protector.get(), "Failed to setup streaming client");
508             priv->loader = nullptr;
509             webKitWebSrcStop(protector.get());
510         }
511     });
512 }
513
514 static GstStateChangeReturn webKitWebSrcChangeState(GstElement* element, GstStateChange transition)
515 {
516     GstStateChangeReturn ret = GST_STATE_CHANGE_SUCCESS;
517     WebKitWebSrc* src = WEBKIT_WEB_SRC(element);
518     WebKitWebSrcPrivate* priv = src->priv;
519
520     switch (transition) {
521     case GST_STATE_CHANGE_NULL_TO_READY:
522         if (!priv->appsrc) {
523             gst_element_post_message(element,
524                                      gst_missing_element_message_new(element, "appsrc"));
525             GST_ELEMENT_ERROR(src, CORE, MISSING_PLUGIN, (nullptr), ("no appsrc"));
526             return GST_STATE_CHANGE_FAILURE;
527         }
528         break;
529     default:
530         break;
531     }
532
533     ret = GST_ELEMENT_CLASS(parent_class)->change_state(element, transition);
534     if (G_UNLIKELY(ret == GST_STATE_CHANGE_FAILURE)) {
535         GST_DEBUG_OBJECT(src, "State change failed");
536         return ret;
537     }
538
539     switch (transition) {
540     case GST_STATE_CHANGE_READY_TO_PAUSED:
541     {
542         GST_DEBUG_OBJECT(src, "READY->PAUSED");
543         webKitWebSrcStart(src);
544         break;
545     }
546     case GST_STATE_CHANGE_PAUSED_TO_READY:
547     {
548         GST_DEBUG_OBJECT(src, "PAUSED->READY");
549         webKitWebSrcStop(src);
550         break;
551     }
552     default:
553         break;
554     }
555
556     return ret;
557 }
558
559 static gboolean webKitWebSrcQueryWithParent(GstPad* pad, GstObject* parent, GstQuery* query)
560 {
561     WebKitWebSrc* src = WEBKIT_WEB_SRC(GST_ELEMENT(parent));
562     WebKitWebSrcPrivate* priv = src->priv;
563     gboolean result = FALSE;
564
565     switch (GST_QUERY_TYPE(query)) {
566     case GST_QUERY_DURATION: {
567         GstFormat format;
568
569         gst_query_parse_duration(query, &format, nullptr);
570
571         GST_LOG_OBJECT(src, "duration query in format %s, current size: %lu", gst_format_get_name(format), priv->size);
572         if (format == GST_FORMAT_BYTES && priv->size > 0) {
573             gst_query_set_duration(query, format, priv->size);
574             result = TRUE;
575         }
576         break;
577     }
578     case GST_QUERY_URI: {
579         gst_query_set_uri(query, priv->originalURI.data());
580         if (!priv->redirectedURI.isNull())
581             gst_query_set_uri_redirection(query, priv->redirectedURI.data());
582         result = TRUE;
583         break;
584     }
585     case GST_QUERY_SCHEDULING: {
586         GstSchedulingFlags flags;
587         int minSize, maxSize, align;
588
589         gst_query_parse_scheduling(query, &flags, &minSize, &maxSize, &align);
590         gst_query_set_scheduling(query, static_cast<GstSchedulingFlags>(flags | GST_SCHEDULING_FLAG_BANDWIDTH_LIMITED), minSize, maxSize, align);
591         result = TRUE;
592         break;
593     }
594     default: {
595         GRefPtr<GstPad> target = adoptGRef(gst_ghost_pad_get_target(GST_GHOST_PAD_CAST(pad)));
596
597         // Forward the query to the proxy target pad.
598         if (target)
599             result = gst_pad_query(target.get(), query);
600         break;
601     }
602     }
603
604     return result;
605 }
606
607 static bool urlHasSupportedProtocol(const URL& url)
608 {
609     return url.isValid() && (url.protocolIsInHTTPFamily() || url.protocolIsBlob());
610 }
611
612 // uri handler interface
613
614 static GstURIType webKitWebSrcUriGetType(GType)
615 {
616     return GST_URI_SRC;
617 }
618
619 const gchar* const* webKitWebSrcGetProtocols(GType)
620 {
621     static const char* protocols[] = {"webkit+http", "webkit+https", "webkit+blob", nullptr };
622     return protocols;
623 }
624
625 static URL convertPlaybinURI(const char* uriString)
626 {
627     URL url(URL(), uriString);
628     ASSERT(url.protocol().substring(0, 7) == "webkit+");
629     url.setProtocol(url.protocol().substring(7).toString());
630     return url;
631 }
632
633 static gchar* webKitWebSrcGetUri(GstURIHandler* handler)
634 {
635     WebKitWebSrc* src = WEBKIT_WEB_SRC(handler);
636     gchar* ret = g_strdup(src->priv->originalURI.data());
637     return ret;
638 }
639
640 static gboolean webKitWebSrcSetUri(GstURIHandler* handler, const gchar* uri, GError** error)
641 {
642     WebKitWebSrc* src = WEBKIT_WEB_SRC(handler);
643     WebKitWebSrcPrivate* priv = src->priv;
644
645     if (GST_STATE(src) >= GST_STATE_PAUSED) {
646         GST_ERROR_OBJECT(src, "URI can only be set in states < PAUSED");
647         return FALSE;
648     }
649
650     priv->redirectedURI = CString();
651     priv->originalURI = CString();
652     if (!uri)
653         return TRUE;
654
655     URL url = convertPlaybinURI(uri);
656     if (!urlHasSupportedProtocol(url)) {
657         g_set_error(error, GST_URI_ERROR, GST_URI_ERROR_BAD_URI, "Invalid URI '%s'", uri);
658         return FALSE;
659     }
660
661     priv->originalURI = url.string().utf8();
662     return TRUE;
663 }
664
665 static void webKitWebSrcUriHandlerInit(gpointer gIface, gpointer)
666 {
667     GstURIHandlerInterface* iface = (GstURIHandlerInterface *) gIface;
668
669     iface->get_type = webKitWebSrcUriGetType;
670     iface->get_protocols = webKitWebSrcGetProtocols;
671     iface->get_uri = webKitWebSrcGetUri;
672     iface->set_uri = webKitWebSrcSetUri;
673 }
674
675 static void webKitWebSrcNeedData(WebKitWebSrc* src)
676 {
677     WebKitWebSrcPrivate* priv = src->priv;
678
679     GST_LOG_OBJECT(src, "Need more data");
680
681     if (!priv->paused)
682         return;
683     priv->paused = false;
684
685     GRefPtr<WebKitWebSrc> protector = WTF::ensureGRef(src);
686     priv->notifier->notify(MainThreadSourceNotification::NeedData, [protector] {
687         WebKitWebSrcPrivate* priv = protector->priv;
688         if (priv->resource)
689             priv->resource->setDefersLoading(false);
690     });
691 }
692
693 static void webKitWebSrcEnoughData(WebKitWebSrc* src)
694 {
695     WebKitWebSrcPrivate* priv = src->priv;
696
697     GST_DEBUG_OBJECT(src, "Have enough data");
698
699     if (priv->paused)
700         return;
701     priv->paused = true;
702
703     GRefPtr<WebKitWebSrc> protector = WTF::ensureGRef(src);
704     priv->notifier->notify(MainThreadSourceNotification::EnoughData, [protector] {
705         WebKitWebSrcPrivate* priv = protector->priv;
706         if (priv->resource)
707             priv->resource->setDefersLoading(true);
708     });
709 }
710
711 static gboolean webKitWebSrcSeek(WebKitWebSrc* src, guint64 offset)
712 {
713     WebKitWebSrcPrivate* priv = src->priv;
714
715     if (offset == priv->offset && priv->requestedOffset == priv->offset)
716         return TRUE;
717
718     if (!priv->seekable)
719         return FALSE;
720
721     priv->isSeeking = true;
722     priv->requestedOffset = offset;
723
724     GST_DEBUG_OBJECT(src, "Seeking to offset: %" G_GUINT64_FORMAT, src->priv->requestedOffset);
725
726     GRefPtr<WebKitWebSrc> protector = WTF::ensureGRef(src);
727     priv->notifier->notify(MainThreadSourceNotification::Seek, [protector] {
728         webKitWebSrcStop(protector.get());
729         webKitWebSrcStart(protector.get());
730     });
731     return TRUE;
732 }
733
734 void webKitWebSrcSetMediaPlayer(WebKitWebSrc* src, WebCore::MediaPlayer* player)
735 {
736     ASSERT(player);
737     src->priv->player = player;
738 }
739
740 bool webKitSrcPassedCORSAccessCheck(WebKitWebSrc* src)
741 {
742     return src->priv->didPassAccessControlCheck;
743 }
744
745 CachedResourceStreamingClient::CachedResourceStreamingClient(WebKitWebSrc* src, ResourceRequest&& request)
746     : m_src(GST_ELEMENT(src))
747     , m_request(WTFMove(request))
748 {
749 }
750
751 CachedResourceStreamingClient::~CachedResourceStreamingClient() = default;
752
753 void CachedResourceStreamingClient::responseReceived(PlatformMediaResource&, const ResourceResponse& response)
754 {
755     WebKitWebSrc* src = WEBKIT_WEB_SRC(m_src.get());
756     WebKitWebSrcPrivate* priv = src->priv;
757     priv->didPassAccessControlCheck = priv->resource->didPassAccessControlCheck();
758
759     GST_DEBUG_OBJECT(src, "Received response: %d", response.httpStatusCode());
760
761     auto responseURI = response.url().string().utf8();
762     if (priv->originalURI != responseURI)
763         priv->redirectedURI = WTFMove(responseURI);
764
765     if (response.httpStatusCode() >= 400) {
766         GST_ELEMENT_ERROR(src, RESOURCE, READ, ("Received %d HTTP error code", response.httpStatusCode()), (nullptr));
767         gst_app_src_end_of_stream(priv->appsrc);
768         webKitWebSrcStop(src);
769         return;
770     }
771
772     if (priv->isSeeking) {
773         GST_DEBUG_OBJECT(src, "Seek in progress, ignoring response");
774         return;
775     }
776
777     if (priv->requestedOffset) {
778         // Seeking ... we expect a 206 == PARTIAL_CONTENT
779         if (response.httpStatusCode() == 200) {
780             // Range request didn't have a ranged response; resetting offset.
781             priv->offset = 0;
782         } else if (response.httpStatusCode() != 206) {
783             // Range request completely failed.
784             GST_ELEMENT_ERROR(src, RESOURCE, READ, ("Received unexpected %d HTTP status code", response.httpStatusCode()), (nullptr));
785             gst_app_src_end_of_stream(priv->appsrc);
786             webKitWebSrcStop(src);
787             return;
788         }
789     }
790
791     long long length = response.expectedContentLength();
792     if (length > 0 && priv->requestedOffset && response.httpStatusCode() == 206)
793         length += priv->requestedOffset;
794
795     priv->size = length >= 0 ? length : 0;
796     priv->seekable = length > 0 && g_ascii_strcasecmp("none", response.httpHeaderField(HTTPHeaderName::AcceptRanges).utf8().data());
797
798     GST_DEBUG_OBJECT(src, "Size: %" G_GINT64_FORMAT ", seekable: %s", priv->size, priv->seekable ? "yes" : "no");
799     // notify size/duration
800     if (length > 0)
801         gst_app_src_set_size(priv->appsrc, length);
802     else {
803         gst_app_src_set_size(priv->appsrc, -1);
804         if (!priv->seekable)
805             gst_app_src_set_stream_type(priv->appsrc, GST_APP_STREAM_TYPE_STREAM);
806     }
807
808     // Signal to downstream if this is an Icecast stream.
809     GRefPtr<GstCaps> caps;
810     String metadataIntervalAsString = response.httpHeaderField(HTTPHeaderName::IcyMetaInt);
811     if (!metadataIntervalAsString.isEmpty()) {
812         bool isMetadataIntervalParsed;
813         int metadataInterval = metadataIntervalAsString.toInt(&isMetadataIntervalParsed);
814         if (isMetadataIntervalParsed && metadataInterval > 0) {
815             caps = adoptGRef(gst_caps_new_simple("application/x-icy", "metadata-interval", G_TYPE_INT, metadataInterval, nullptr));
816
817             String contentType = response.httpHeaderField(HTTPHeaderName::ContentType);
818             GST_DEBUG_OBJECT(src, "Response ContentType: %s", contentType.utf8().data());
819             gst_caps_set_simple(caps.get(), "content-type", G_TYPE_STRING, contentType.utf8().data(), nullptr);
820
821             gst_app_src_set_stream_type(priv->appsrc, GST_APP_STREAM_TYPE_STREAM);
822         }
823     }
824
825     gst_app_src_set_caps(priv->appsrc, caps.get());
826
827     // Emit a GST_EVENT_CUSTOM_DOWNSTREAM_STICKY event and message to let
828     // GStreamer know about the HTTP headers sent and received.
829     GstStructure* httpHeaders = gst_structure_new_empty("http-headers");
830     gst_structure_set(httpHeaders, "uri", G_TYPE_STRING, priv->originalURI.data(),
831         "http-status-code", G_TYPE_UINT, response.httpStatusCode(), nullptr);
832     if (!priv->redirectedURI.isNull())
833         gst_structure_set(httpHeaders, "redirection-uri", G_TYPE_STRING, priv->redirectedURI.data(), nullptr);
834     GUniquePtr<GstStructure> headers(gst_structure_new_empty("request-headers"));
835     for (const auto& header : m_request.httpHeaderFields())
836         gst_structure_set(headers.get(), header.key.utf8().data(), G_TYPE_STRING, header.value.utf8().data(), nullptr);
837     GST_DEBUG_OBJECT(src, "Request headers going downstream: %" GST_PTR_FORMAT, headers.get());
838     gst_structure_set(httpHeaders, "request-headers", GST_TYPE_STRUCTURE, headers.get(), nullptr);
839     headers.reset(gst_structure_new_empty("response-headers"));
840     for (const auto& header : response.httpHeaderFields())
841         gst_structure_set(headers.get(), header.key.utf8().data(), G_TYPE_STRING, header.value.utf8().data(), nullptr);
842     gst_structure_set(httpHeaders, "response-headers", GST_TYPE_STRUCTURE, headers.get(), nullptr);
843     GST_DEBUG_OBJECT(src, "Response headers going downstream: %" GST_PTR_FORMAT, headers.get());
844
845     gst_element_post_message(GST_ELEMENT_CAST(src), gst_message_new_element(GST_OBJECT_CAST(src),
846         gst_structure_copy(httpHeaders)));
847     gst_pad_push_event(GST_BASE_SRC_PAD(priv->appsrc), gst_event_new_custom(GST_EVENT_CUSTOM_DOWNSTREAM_STICKY, httpHeaders));
848 }
849
850 void CachedResourceStreamingClient::dataReceived(PlatformMediaResource&, const char* data, int length)
851 {
852     WebKitWebSrc* src = WEBKIT_WEB_SRC(m_src.get());
853     WebKitWebSrcPrivate* priv = src->priv;
854
855     GST_LOG_OBJECT(src, "Have %lld bytes of data", priv->buffer ? static_cast<long long>(gst_buffer_get_size(priv->buffer.get())) : length);
856
857     ASSERT(!priv->buffer || data == getGstBufferDataPointer(priv->buffer.get()));
858
859     if (priv->buffer)
860         unmapGstBuffer(priv->buffer.get());
861
862     if (priv->isSeeking) {
863         GST_DEBUG_OBJECT(src, "Seek in progress, ignoring data");
864         priv->buffer.clear();
865         return;
866     }
867
868     if (priv->offset < priv->requestedOffset) {
869         // Range request failed; seeking manually.
870         if (priv->offset + length <= priv->requestedOffset) {
871             // Discard all the buffers coming before the requested seek position.
872             priv->offset += length;
873             priv->buffer.clear();
874             return;
875         }
876
877         if (priv->offset + length > priv->requestedOffset) {
878             guint64 offset = priv->requestedOffset - priv->offset;
879             data += offset;
880             length -= offset;
881             if (priv->buffer)
882                 gst_buffer_resize(priv->buffer.get(), offset, -1);
883             priv->offset = priv->requestedOffset;
884         }
885
886         priv->requestedOffset = 0;
887     }
888
889     // Ports using the GStreamer backend but not the soup implementation of ResourceHandle
890     // won't be using buffers provided by this client, the buffer is created here in that case.
891     if (!priv->buffer)
892         priv->buffer = adoptGRef(createGstBufferForData(data, length));
893     else
894         gst_buffer_set_size(priv->buffer.get(), static_cast<gssize>(length));
895
896     uint64_t startingOffset = priv->offset;
897
898     if (priv->requestedOffset == priv->offset)
899         priv->requestedOffset += length;
900     priv->offset += length;
901     // priv->size == 0 if received length on didReceiveResponse < 0.
902     if (priv->size > 0 && priv->offset > priv->size) {
903         GST_DEBUG_OBJECT(src, "Updating internal size from %" G_GUINT64_FORMAT " to %" G_GUINT64_FORMAT, priv->size, priv->offset);
904         gst_app_src_set_size(priv->appsrc, priv->offset);
905         priv->size = priv->offset;
906     }
907
908     // Now split the recv'd buffer into buffers that are of a size basesrc suggests. It is important not
909     // to push buffers that are too large, otherwise incorrect buffering messages can be sent from the
910     // pipeline.
911     uint64_t bufferSize = gst_buffer_get_size(priv->buffer.get());
912     uint64_t blockSize = static_cast<uint64_t>(GST_BASE_SRC_CAST(priv->appsrc)->blocksize);
913     ASSERT(blockSize);
914     GST_LOG_OBJECT(src, "Splitting the received buffer into %" PRIu64 " blocks", bufferSize / blockSize);
915     for (uint64_t currentOffset = 0; currentOffset < bufferSize; currentOffset += blockSize) {
916         uint64_t subBufferOffset = startingOffset + currentOffset;
917         uint64_t currentOffsetSize = std::min(blockSize, bufferSize - currentOffset);
918
919         GST_TRACE_OBJECT(src, "Create sub-buffer from [%" PRIu64 ", %" PRIu64 "]", currentOffset, currentOffset + currentOffsetSize);
920         GstBuffer* subBuffer = gst_buffer_copy_region(priv->buffer.get(), GST_BUFFER_COPY_ALL, currentOffset, currentOffsetSize);
921         if (UNLIKELY(!subBuffer)) {
922             GST_ELEMENT_ERROR(src, CORE, FAILED, ("Failed to allocate sub-buffer"), (nullptr));
923             break;
924         }
925
926         GST_BUFFER_OFFSET(subBuffer) = subBufferOffset;
927         GST_BUFFER_OFFSET_END(subBuffer) = subBufferOffset + currentOffsetSize;
928         GST_TRACE_OBJECT(src, "Set sub-buffer offset bounds [%" PRIu64 ", %" PRIu64 "]", GST_BUFFER_OFFSET(subBuffer), GST_BUFFER_OFFSET_END(subBuffer));
929
930         GST_TRACE_OBJECT(src, "Pushing buffer of size %" G_GSIZE_FORMAT " bytes", gst_buffer_get_size(subBuffer));
931         GstFlowReturn ret = gst_app_src_push_buffer(priv->appsrc, subBuffer);
932
933         if (UNLIKELY(ret != GST_FLOW_OK && ret != GST_FLOW_EOS && ret != GST_FLOW_FLUSHING)) {
934             GST_ELEMENT_ERROR(src, CORE, FAILED, (nullptr), (nullptr));
935             break;
936         }
937     }
938
939     priv->buffer.clear();
940 }
941
942 void CachedResourceStreamingClient::accessControlCheckFailed(PlatformMediaResource&, const ResourceError& error)
943 {
944     WebKitWebSrc* src = WEBKIT_WEB_SRC(m_src.get());
945     GST_ELEMENT_ERROR(src, RESOURCE, READ, ("%s", error.localizedDescription().utf8().data()), (nullptr));
946     gst_app_src_end_of_stream(src->priv->appsrc);
947     webKitWebSrcStop(src);
948 }
949
950 void CachedResourceStreamingClient::loadFailed(PlatformMediaResource&, const ResourceError& error)
951 {
952     WebKitWebSrc* src = WEBKIT_WEB_SRC(m_src.get());
953
954     if (!error.isCancellation()) {
955         GST_ERROR_OBJECT(src, "Have failure: %s", error.localizedDescription().utf8().data());
956         GST_ELEMENT_ERROR(src, RESOURCE, FAILED, ("%s", error.localizedDescription().utf8().data()), (nullptr));
957     }
958
959     gst_app_src_end_of_stream(src->priv->appsrc);
960 }
961
962 void CachedResourceStreamingClient::loadFinished(PlatformMediaResource&)
963 {
964     WebKitWebSrc* src = WEBKIT_WEB_SRC(m_src.get());
965     WebKitWebSrcPrivate* priv = src->priv;
966
967     GST_DEBUG_OBJECT(src, "Have EOS");
968
969     if (!priv->isSeeking)
970         gst_app_src_end_of_stream(priv->appsrc);
971 }
972
973 #endif // ENABLE(VIDEO) && USE(GSTREAMER)