[GStreamer] Remove the HLS queue buffering query hack
[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  *  Copyright (C) 2019 Igalia S.L.
5  *
6  *  This library is free software; you can redistribute it and/or
7  *  modify it under the terms of the GNU Lesser General Public
8  *  License as published by the Free Software Foundation; either
9  *  version 2 of the License, or (at your option) any later version.
10  *
11  *  This library is distributed in the hope that it will be useful,
12  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
13  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14  *  Lesser General Public License for more details.
15  *
16  *  You should have received a copy of the GNU Lesser General Public
17  *  License along with this library; if not, write to the Free Software
18  *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
19  */
20
21 #include "config.h"
22 #include "WebKitWebSourceGStreamer.h"
23
24 #if ENABLE(VIDEO) && USE(GSTREAMER)
25
26 #include "GStreamerCommon.h"
27 #include "HTTPHeaderNames.h"
28 #include "MainThreadNotifier.h"
29 #include "MediaPlayer.h"
30 #include "PlatformMediaResourceLoader.h"
31 #include "ResourceError.h"
32 #include "ResourceRequest.h"
33 #include "ResourceResponse.h"
34 #include <cstdint>
35 #include <wtf/Condition.h>
36 #include <wtf/Scope.h>
37 #include <wtf/text/CString.h>
38
39 using namespace WebCore;
40
41 class CachedResourceStreamingClient final : public PlatformMediaResourceClient {
42     WTF_MAKE_NONCOPYABLE(CachedResourceStreamingClient);
43 public:
44     CachedResourceStreamingClient(WebKitWebSrc*, ResourceRequest&&);
45     virtual ~CachedResourceStreamingClient();
46
47 private:
48     void checkUpdateBlocksize(uint64_t bytesRead);
49
50     // PlatformMediaResourceClient virtual methods.
51     void responseReceived(PlatformMediaResource&, const ResourceResponse&, CompletionHandler<void(ShouldContinue)>&&) override;
52     void dataReceived(PlatformMediaResource&, const char*, int) override;
53     void accessControlCheckFailed(PlatformMediaResource&, const ResourceError&) override;
54     void loadFailed(PlatformMediaResource&, const ResourceError&) override;
55     void loadFinished(PlatformMediaResource&) override;
56
57     static constexpr int s_growBlocksizeLimit { 1 };
58     static constexpr int s_growBlocksizeCount { 1 };
59     static constexpr int s_growBlocksizeFactor { 2 };
60     static constexpr float s_reduceBlocksizeLimit { 0.20 };
61     static constexpr int s_reduceBlocksizeCount { 2 };
62     static constexpr float s_reduceBlocksizeFactor { 0.5 };
63     int m_reduceBlocksizeCount { 0 };
64     int m_increaseBlocksizeCount { 0 };
65
66     GRefPtr<GstElement> m_src;
67     ResourceRequest m_request;
68 };
69
70 enum MainThreadSourceNotification {
71     Start = 1 << 0,
72     Stop = 1 << 1,
73 };
74
75 #define WEBKIT_WEB_SRC_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE((obj), WEBKIT_TYPE_WEB_SRC, WebKitWebSrcPrivate))
76 struct _WebKitWebSrcPrivate {
77     CString originalURI;
78     CString redirectedURI;
79     bool keepAlive;
80     GUniquePtr<GstStructure> extraHeaders;
81     bool compress;
82     GUniquePtr<gchar> httpMethod;
83     WebCore::MediaPlayer* player;
84     RefPtr<PlatformMediaResourceLoader> loader;
85     RefPtr<PlatformMediaResource> resource;
86     RefPtr<MainThreadNotifier<MainThreadSourceNotification>> notifier;
87     bool didPassAccessControlCheck;
88     bool wereHeadersReceived;
89     Condition headersCondition;
90     Lock responseLock;
91     bool wasResponseReceived;
92     Condition responseCondition;
93     bool doesHaveEOS;
94     bool isFlushing { false };
95     uint64_t readPosition;
96     uint64_t requestedPosition;
97     uint64_t stopPosition;
98     bool isDurationSet;
99     bool haveSize;
100     uint64_t size;
101     bool isSeekable;
102     bool isSeeking;
103     bool wasSeeking { false };
104     uint64_t minimumBlocksize;
105     Lock adapterLock;
106     Condition adapterCondition;
107     uint64_t queueSize { 0 };
108     GRefPtr<GstAdapter> adapter;
109     GRefPtr<GstEvent> httpHeadersEvent;
110     GUniquePtr<GstStructure> httpHeaders;
111 };
112
113 enum {
114     PROP_0,
115     PROP_LOCATION,
116     PROP_RESOLVED_LOCATION,
117     PROP_KEEP_ALIVE,
118     PROP_EXTRA_HEADERS,
119     PROP_COMPRESS,
120     PROP_METHOD
121 };
122
123 static GstStaticPadTemplate srcTemplate = GST_STATIC_PAD_TEMPLATE("src", GST_PAD_SRC, GST_PAD_ALWAYS, GST_STATIC_CAPS_ANY);
124
125 GST_DEBUG_CATEGORY_STATIC(webkit_web_src_debug);
126 #define GST_CAT_DEFAULT webkit_web_src_debug
127
128 static void webKitWebSrcUriHandlerInit(gpointer gIface, gpointer ifaceData);
129
130 static void webKitWebSrcDispose(GObject*);
131 static void webKitWebSrcFinalize(GObject*);
132 static void webKitWebSrcSetProperty(GObject*, guint propertyID, const GValue*, GParamSpec*);
133 static void webKitWebSrcGetProperty(GObject*, guint propertyID, GValue*, GParamSpec*);
134 static GstStateChangeReturn webKitWebSrcChangeState(GstElement*, GstStateChange);
135 static GstFlowReturn webKitWebSrcCreate(GstPushSrc*, GstBuffer**);
136 static gboolean webKitWebSrcStart(GstBaseSrc*);
137 static gboolean webKitWebSrcStop(GstBaseSrc*);
138 static gboolean webKitWebSrcGetSize(GstBaseSrc*, guint64* size);
139 static gboolean webKitWebSrcIsSeekable(GstBaseSrc*);
140 static gboolean webKitWebSrcDoSeek(GstBaseSrc*, GstSegment*);
141 static gboolean webKitWebSrcQuery(GstBaseSrc*, GstQuery*);
142 static gboolean webKitWebSrcUnLock(GstBaseSrc*);
143 static gboolean webKitWebSrcUnLockStop(GstBaseSrc*);
144 static void webKitWebSrcSetContext(GstElement*, GstContext*);
145
146 #define webkit_web_src_parent_class parent_class
147 // We split this out into another macro to avoid a check-webkit-style error.
148 #define WEBKIT_WEB_SRC_CATEGORY_INIT GST_DEBUG_CATEGORY_INIT(webkit_web_src_debug, "webkitwebsrc", 0, "websrc element");
149 G_DEFINE_TYPE_WITH_CODE(WebKitWebSrc, webkit_web_src, GST_TYPE_PUSH_SRC,
150     G_IMPLEMENT_INTERFACE(GST_TYPE_URI_HANDLER, webKitWebSrcUriHandlerInit);
151     WEBKIT_WEB_SRC_CATEGORY_INIT);
152
153 static void webkit_web_src_class_init(WebKitWebSrcClass* klass)
154 {
155     GObjectClass* oklass = G_OBJECT_CLASS(klass);
156
157     oklass->dispose = webKitWebSrcDispose;
158     oklass->finalize = webKitWebSrcFinalize;
159     oklass->set_property = webKitWebSrcSetProperty;
160     oklass->get_property = webKitWebSrcGetProperty;
161
162     GstElementClass* eklass = GST_ELEMENT_CLASS(klass);
163     gst_element_class_add_static_pad_template(eklass, &srcTemplate);
164
165     gst_element_class_set_metadata(eklass, "WebKit Web source element", "Source", "Handles HTTP/HTTPS uris",
166         "Philippe Normand <philn@igalia.com>");
167
168     /* Allows setting the uri using the 'location' property, which is used
169      * for example by gst_element_make_from_uri() */
170     g_object_class_install_property(oklass, PROP_LOCATION,
171         g_param_spec_string("location", "location", "Location to read from",
172             nullptr, static_cast<GParamFlags>(G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)));
173
174     g_object_class_install_property(oklass, PROP_RESOLVED_LOCATION,
175         g_param_spec_string("resolved-location", "Resolved location", "The location resolved by the server",
176             nullptr, static_cast<GParamFlags>(G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)));
177
178     g_object_class_install_property(oklass, PROP_KEEP_ALIVE,
179         g_param_spec_boolean("keep-alive", "keep-alive", "Use HTTP persistent connections",
180             FALSE, static_cast<GParamFlags>(G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)));
181
182     g_object_class_install_property(oklass, PROP_EXTRA_HEADERS,
183         g_param_spec_boxed("extra-headers", "Extra Headers", "Extra headers to append to the HTTP request",
184             GST_TYPE_STRUCTURE, static_cast<GParamFlags>(G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)));
185
186     g_object_class_install_property(oklass, PROP_COMPRESS,
187         g_param_spec_boolean("compress", "Compress", "Allow compressed content encodings",
188             FALSE, static_cast<GParamFlags>(G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)));
189
190     g_object_class_install_property(oklass, PROP_METHOD,
191         g_param_spec_string("method", "method", "The HTTP method to use (default: GET)",
192             nullptr, static_cast<GParamFlags>(G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)));
193
194     eklass->change_state = GST_DEBUG_FUNCPTR(webKitWebSrcChangeState);
195     eklass->set_context = GST_DEBUG_FUNCPTR(webKitWebSrcSetContext);
196
197     GstBaseSrcClass* baseSrcClass = GST_BASE_SRC_CLASS(klass);
198     baseSrcClass->start = GST_DEBUG_FUNCPTR(webKitWebSrcStart);
199     baseSrcClass->stop = GST_DEBUG_FUNCPTR(webKitWebSrcStop);
200     baseSrcClass->unlock = GST_DEBUG_FUNCPTR(webKitWebSrcUnLock);
201     baseSrcClass->unlock_stop = GST_DEBUG_FUNCPTR(webKitWebSrcUnLockStop);
202     baseSrcClass->get_size = GST_DEBUG_FUNCPTR(webKitWebSrcGetSize);
203     baseSrcClass->is_seekable = GST_DEBUG_FUNCPTR(webKitWebSrcIsSeekable);
204     baseSrcClass->do_seek = GST_DEBUG_FUNCPTR(webKitWebSrcDoSeek);
205     baseSrcClass->query = GST_DEBUG_FUNCPTR(webKitWebSrcQuery);
206
207     GstPushSrcClass* pushSrcClass = GST_PUSH_SRC_CLASS(klass);
208     pushSrcClass->create = GST_DEBUG_FUNCPTR(webKitWebSrcCreate);
209
210     g_type_class_add_private(klass, sizeof(WebKitWebSrcPrivate));
211 }
212
213
214 static void webkitWebSrcReset(WebKitWebSrc* src)
215 {
216     WebKitWebSrcPrivate* priv = WEBKIT_WEB_SRC_GET_PRIVATE(src);
217
218     priv->haveSize = false;
219     priv->wereHeadersReceived = false;
220     priv->isSeekable = false;
221     priv->readPosition = 0;
222     priv->requestedPosition = 0;
223     priv->stopPosition = -1;
224     priv->size = 0;
225 }
226
227 static void webkit_web_src_init(WebKitWebSrc* src)
228 {
229     WebKitWebSrcPrivate* priv = WEBKIT_WEB_SRC_GET_PRIVATE(src);
230
231     src->priv = priv;
232     new (priv) WebKitWebSrcPrivate();
233
234     priv->notifier = MainThreadNotifier<MainThreadSourceNotification>::create();
235     priv->adapter = adoptGRef(gst_adapter_new());
236     priv->minimumBlocksize = gst_base_src_get_blocksize(GST_BASE_SRC_CAST(src));
237
238     webkitWebSrcReset(src);
239     gst_base_src_set_automatic_eos(GST_BASE_SRC_CAST(src), FALSE);
240 }
241
242 static void webKitWebSrcDispose(GObject* object)
243 {
244     WebKitWebSrcPrivate* priv = WEBKIT_WEB_SRC(object)->priv;
245     if (priv->notifier) {
246         priv->notifier->invalidate();
247         priv->notifier = nullptr;
248     }
249
250     GST_CALL_PARENT(G_OBJECT_CLASS, dispose, (object));
251 }
252
253 static void webKitWebSrcFinalize(GObject* object)
254 {
255     WebKitWebSrcPrivate* priv = WEBKIT_WEB_SRC(object)->priv;
256
257     priv->~WebKitWebSrcPrivate();
258
259     GST_CALL_PARENT(G_OBJECT_CLASS, finalize, (object));
260 }
261
262 static void webKitWebSrcSetProperty(GObject* object, guint propID, const GValue* value, GParamSpec* pspec)
263 {
264     WebKitWebSrc* src = WEBKIT_WEB_SRC(object);
265
266     switch (propID) {
267     case PROP_LOCATION:
268         gst_uri_handler_set_uri(reinterpret_cast<GstURIHandler*>(src), g_value_get_string(value), nullptr);
269         break;
270     case PROP_KEEP_ALIVE:
271         src->priv->keepAlive = g_value_get_boolean(value);
272         break;
273     case PROP_EXTRA_HEADERS: {
274         const GstStructure* s = gst_value_get_structure(value);
275         src->priv->extraHeaders.reset(s ? gst_structure_copy(s) : nullptr);
276         break;
277     }
278     case PROP_COMPRESS:
279         src->priv->compress = g_value_get_boolean(value);
280         break;
281     case PROP_METHOD:
282         src->priv->httpMethod.reset(g_value_dup_string(value));
283         break;
284     default:
285         G_OBJECT_WARN_INVALID_PROPERTY_ID(object, propID, pspec);
286         break;
287     }
288 }
289
290 static void webKitWebSrcGetProperty(GObject* object, guint propID, GValue* value, GParamSpec* pspec)
291 {
292     WebKitWebSrc* src = WEBKIT_WEB_SRC(object);
293     WebKitWebSrcPrivate* priv = src->priv;
294
295     switch (propID) {
296     case PROP_LOCATION:
297         g_value_set_string(value, priv->originalURI.data());
298         break;
299     case PROP_RESOLVED_LOCATION:
300         g_value_set_string(value, priv->redirectedURI.isNull() ? priv->originalURI.data() : priv->redirectedURI.data());
301         break;
302     case PROP_KEEP_ALIVE:
303         g_value_set_boolean(value, priv->keepAlive);
304         break;
305     case PROP_EXTRA_HEADERS:
306         gst_value_set_structure(value, priv->extraHeaders.get());
307         break;
308     case PROP_COMPRESS:
309         g_value_set_boolean(value, priv->compress);
310         break;
311     case PROP_METHOD:
312         g_value_set_string(value, priv->httpMethod.get());
313         break;
314     default:
315         G_OBJECT_WARN_INVALID_PROPERTY_ID(object, propID, pspec);
316         break;
317     }
318 }
319
320 static void webKitWebSrcSetContext(GstElement* element, GstContext* context)
321 {
322     WebKitWebSrc* src = WEBKIT_WEB_SRC(element);
323     WebKitWebSrcPrivate* priv = src->priv;
324
325     GST_DEBUG_OBJECT(src, "context type: %s", gst_context_get_context_type(context));
326     if (gst_context_has_context_type(context, WEBKIT_WEB_SRC_PLAYER_CONTEXT_TYPE_NAME)) {
327         const GValue* value = gst_structure_get_value(gst_context_get_structure(context), "player");
328         priv->player = reinterpret_cast<MediaPlayer*>(g_value_get_pointer(value));
329     }
330     GST_ELEMENT_CLASS(parent_class)->set_context(element, context);
331 }
332
333 static GstFlowReturn webKitWebSrcCreate(GstPushSrc* pushSrc, GstBuffer** buffer)
334 {
335     GstBaseSrc* baseSrc = GST_BASE_SRC_CAST(pushSrc);
336     WebKitWebSrc* src = WEBKIT_WEB_SRC(baseSrc);
337     WebKitWebSrcPrivate* priv = src->priv;
338
339     GST_TRACE_OBJECT(src, "readPosition = %" G_GUINT64_FORMAT " requestedPosition = %" G_GUINT64_FORMAT, priv->readPosition, priv->requestedPosition);
340
341     if (priv->requestedPosition != priv->readPosition) {
342         {
343             LockHolder adapterLocker(priv->adapterLock);
344             GST_DEBUG_OBJECT(src, "Seeking, flushing adapter");
345             // Discard all the buffers coming before the requested seek position.
346             gst_adapter_flush(priv->adapter.get(), priv->queueSize);
347             priv->queueSize = 0;
348         }
349         uint64_t requestedPosition = priv->requestedPosition;
350         webKitWebSrcStop(baseSrc);
351         priv->requestedPosition = requestedPosition;
352         webKitWebSrcStart(baseSrc);
353     }
354
355     {
356         LockHolder locker(priv->responseLock);
357         priv->responseCondition.wait(priv->responseLock, [priv] () {
358             return priv->wasResponseReceived || priv->isFlushing;
359         });
360     }
361
362     GST_TRACE_OBJECT(src, "flushing: %s, doesHaveEOS: %s, queueSize: %" G_GSIZE_FORMAT, boolForPrinting(priv->isFlushing), boolForPrinting(priv->doesHaveEOS), priv->queueSize);
363
364     if (priv->isFlushing) {
365         GST_DEBUG_OBJECT(src, "Flushing");
366         return GST_FLOW_FLUSHING;
367     }
368
369     if (priv->doesHaveEOS) {
370         GST_DEBUG_OBJECT(src, "EOS");
371         return GST_FLOW_EOS;
372     }
373
374     unsigned size = gst_base_src_get_blocksize(baseSrc);
375     bool isAdapterDrained = false;
376     {
377         LockHolder adapterLocker(priv->adapterLock);
378         unsigned retries = 0;
379         size_t available = gst_adapter_available_fast(priv->adapter.get());
380         while (available < size && !isAdapterDrained) {
381             priv->adapterCondition.waitFor(priv->adapterLock, Seconds(1));
382             retries++;
383             available = gst_adapter_available_fast(priv->adapter.get());
384             if (available && available < size)
385                 size = available;
386             else if (retries > 3)
387                 isAdapterDrained = true;
388         }
389     }
390
391     if (isAdapterDrained) {
392         GST_DEBUG_OBJECT(src, "Adapter still empty after 3 seconds of waiting, assuming EOS");
393         return GST_FLOW_EOS;
394     }
395
396     if (priv->haveSize && !priv->isDurationSet) {
397         GST_DEBUG_OBJECT(src, "Setting duration to %" G_GUINT64_FORMAT, priv->size);
398         baseSrc->segment.duration = priv->size;
399         priv->isDurationSet = true;
400         gst_element_post_message(GST_ELEMENT_CAST(src), gst_message_new_duration_changed(GST_OBJECT_CAST(src)));
401     }
402
403     if (priv->httpHeadersEvent)
404         gst_pad_push_event(GST_BASE_SRC_PAD(baseSrc), priv->httpHeadersEvent.leakRef());
405
406     {
407         GST_TRACE_OBJECT(src, "Taking %u bytes from adapter", size);
408         LockHolder adapterLocker(priv->adapterLock);
409         if (size) {
410             *buffer = gst_adapter_take_buffer_fast(priv->adapter.get(), size);
411             RELEASE_ASSERT(*buffer);
412
413             priv->queueSize -= size;
414
415             GST_BUFFER_OFFSET(*buffer) = baseSrc->segment.position;
416             GST_BUFFER_OFFSET_END(*buffer) = GST_BUFFER_OFFSET(*buffer) + size;
417             GST_TRACE_OBJECT(src, "Buffer bounds set to %" G_GUINT64_FORMAT "-%" G_GUINT64_FORMAT, GST_BUFFER_OFFSET(*buffer), GST_BUFFER_OFFSET_END(*buffer));
418             GST_TRACE_OBJECT(src, "doesHaveEOS: %s, wasSeeking: %s, seeking: %s, size: %u", boolForPrinting(priv->doesHaveEOS), boolForPrinting(priv->wasSeeking), boolForPrinting(priv->isSeeking), size);
419             if (priv->haveSize && GST_BUFFER_OFFSET_END(*buffer) >= priv->size) {
420                 if (priv->wasSeeking)
421                     priv->wasSeeking = false;
422                 else
423                     priv->doesHaveEOS = true;
424             } else if (priv->wasSeeking)
425                 priv->wasSeeking = false;
426         } else
427             GST_ERROR_OBJECT(src, "Empty adapter?");
428     }
429
430     return GST_FLOW_OK;
431 }
432
433 static bool webKitWebSrcSetExtraHeader(GQuark fieldId, const GValue* value, gpointer userData)
434 {
435     GUniquePtr<gchar> fieldContent;
436
437     if (G_VALUE_HOLDS_STRING(value))
438         fieldContent.reset(g_value_dup_string(value));
439     else {
440         GValue dest = G_VALUE_INIT;
441
442         g_value_init(&dest, G_TYPE_STRING);
443         if (g_value_transform(value, &dest))
444             fieldContent.reset(g_value_dup_string(&dest));
445     }
446
447     const gchar* fieldName = g_quark_to_string(fieldId);
448     if (!fieldContent.get()) {
449         GST_ERROR("extra-headers field '%s' contains no value or can't be converted to a string", fieldName);
450         return false;
451     }
452
453     GST_DEBUG("Appending extra header: \"%s: %s\"", fieldName, fieldContent.get());
454     ResourceRequest* request = static_cast<ResourceRequest*>(userData);
455     request->setHTTPHeaderField(fieldName, fieldContent.get());
456     return true;
457 }
458
459 static gboolean webKitWebSrcProcessExtraHeaders(GQuark fieldId, const GValue* value, gpointer userData)
460 {
461     if (G_VALUE_TYPE(value) == GST_TYPE_ARRAY) {
462         unsigned size = gst_value_array_get_size(value);
463
464         for (unsigned i = 0; i < size; i++) {
465             if (!webKitWebSrcSetExtraHeader(fieldId, gst_value_array_get_value(value, i), userData))
466                 return FALSE;
467         }
468         return TRUE;
469     }
470
471     if (G_VALUE_TYPE(value) == GST_TYPE_LIST) {
472         unsigned size = gst_value_list_get_size(value);
473
474         for (unsigned i = 0; i < size; i++) {
475             if (!webKitWebSrcSetExtraHeader(fieldId, gst_value_list_get_value(value, i), userData))
476                 return FALSE;
477         }
478         return TRUE;
479     }
480
481     return webKitWebSrcSetExtraHeader(fieldId, value, userData);
482 }
483
484 static gboolean webKitWebSrcStart(GstBaseSrc* baseSrc)
485 {
486     WebKitWebSrc* src = WEBKIT_WEB_SRC(baseSrc);
487     WebKitWebSrcPrivate* priv = src->priv;
488
489     if (!priv->player) {
490         GRefPtr<GstQuery> query = adoptGRef(gst_query_new_context(WEBKIT_WEB_SRC_PLAYER_CONTEXT_TYPE_NAME));
491         if (gst_pad_peer_query(GST_BASE_SRC_PAD(baseSrc), query.get())) {
492             GstContext* context;
493
494             gst_query_parse_context(query.get(), &context);
495             gst_element_set_context(GST_ELEMENT_CAST(src), context);
496         } else
497             gst_element_post_message(GST_ELEMENT_CAST(src), gst_message_new_need_context(GST_OBJECT_CAST(src), WEBKIT_WEB_SRC_PLAYER_CONTEXT_TYPE_NAME));
498     }
499
500     RELEASE_ASSERT(priv->player);
501
502     priv->wereHeadersReceived = false;
503     priv->wasResponseReceived = false;
504     priv->isDurationSet = false;
505     priv->doesHaveEOS = false;
506     priv->isFlushing = false;
507
508     priv->didPassAccessControlCheck = false;
509
510     if (priv->originalURI.isNull()) {
511         GST_ERROR_OBJECT(src, "No URI provided");
512         webKitWebSrcStop(baseSrc);
513         return FALSE;
514     }
515
516     if (priv->requestedPosition == priv->stopPosition) {
517         GST_DEBUG_OBJECT(src, "Empty segment, signaling EOS");
518         priv->doesHaveEOS = true;
519         return FALSE;
520     }
521
522     GST_DEBUG_OBJECT(src, "Fetching %s", priv->originalURI.data());
523     URL url = URL(URL(), priv->originalURI.data());
524
525     ResourceRequest request(url);
526     request.setAllowCookies(true);
527     request.setFirstPartyForCookies(url);
528
529     request.setHTTPReferrer(priv->player->referrer());
530
531     if (priv->httpMethod.get())
532         request.setHTTPMethod(priv->httpMethod.get());
533
534 #if USE(SOUP)
535     // By default, HTTP Accept-Encoding is disabled here as we don't
536     // want the received response to be encoded in any way as we need
537     // to rely on the proper size of the returned data on
538     // didReceiveResponse.
539     // If Accept-Encoding is used, the server may send the data in encoded format and
540     // request.expectedContentLength() will have the "wrong" size (the size of the
541     // compressed data), even though the data received in didReceiveData is uncompressed.
542     // This is however useful to enable for adaptive streaming
543     // scenarios, when the demuxer needs to download playlists.
544     if (!priv->compress)
545         request.setAcceptEncoding(false);
546 #endif
547
548     // Let Apple web servers know we want to access their nice movie trailers.
549     if (!g_ascii_strcasecmp("movies.apple.com", url.host().utf8().data())
550         || !g_ascii_strcasecmp("trailers.apple.com", url.host().utf8().data()))
551         request.setHTTPUserAgent("Quicktime/7.6.6");
552
553     if (priv->requestedPosition) {
554         GUniquePtr<char> formatedRange(g_strdup_printf("bytes=%" G_GUINT64_FORMAT "-", priv->requestedPosition));
555         GST_DEBUG_OBJECT(src, "Range request: %s", formatedRange.get());
556         request.setHTTPHeaderField(HTTPHeaderName::Range, formatedRange.get());
557     }
558     priv->readPosition = priv->requestedPosition;
559
560     GST_DEBUG_OBJECT(src, "Persistent connection support %s", priv->keepAlive ? "enabled" : "disabled");
561     if (!priv->keepAlive)
562         request.setHTTPHeaderField(HTTPHeaderName::Connection, "close");
563
564     if (priv->extraHeaders)
565         gst_structure_foreach(priv->extraHeaders.get(), webKitWebSrcProcessExtraHeaders, &request);
566
567     // We always request Icecast/Shoutcast metadata, just in case ...
568     request.setHTTPHeaderField(HTTPHeaderName::IcyMetadata, "1");
569
570     GRefPtr<WebKitWebSrc> protector = WTF::ensureGRef(src);
571     priv->notifier->notifyAndWait(MainThreadSourceNotification::Start, [protector, request = WTFMove(request)] {
572         WebKitWebSrcPrivate* priv = protector->priv;
573         if (!priv->loader)
574             priv->loader = priv->player->createResourceLoader();
575
576         PlatformMediaResourceLoader::LoadOptions loadOptions = 0;
577         if (request.url().protocolIsBlob())
578             loadOptions |= PlatformMediaResourceLoader::LoadOption::BufferData;
579         priv->resource = priv->loader->requestResource(ResourceRequest(request), loadOptions);
580         if (priv->resource) {
581             priv->resource->setClient(std::make_unique<CachedResourceStreamingClient>(protector.get(), ResourceRequest(request)));
582             GST_DEBUG_OBJECT(protector.get(), "Started request");
583         } else {
584             GST_ERROR_OBJECT(protector.get(), "Failed to setup streaming client");
585             priv->loader = nullptr;
586         }
587     });
588
589     GST_DEBUG_OBJECT(src, "Resource loader started");
590     return TRUE;
591 }
592
593 static void webKitWebSrcCloseSession(WebKitWebSrc* src)
594 {
595     WebKitWebSrcPrivate* priv = src->priv;
596     GRefPtr<WebKitWebSrc> protector = WTF::ensureGRef(src);
597
598     priv->notifier->notifyAndWait(MainThreadSourceNotification::Stop, [protector, keepAlive = priv->keepAlive] {
599         WebKitWebSrcPrivate* priv = protector->priv;
600
601         GST_DEBUG_OBJECT(protector.get(), "Stopping resource loader");
602
603         if (priv->resource) {
604             priv->resource->stop();
605             priv->resource->setClient(nullptr);
606             priv->resource = nullptr;
607         }
608
609         if (!keepAlive)
610             priv->loader = nullptr;
611     });
612
613     GST_DEBUG_OBJECT(src, "Resource loader stopped");
614 }
615
616 static gboolean webKitWebSrcStop(GstBaseSrc* baseSrc)
617 {
618     WebKitWebSrc* src = WEBKIT_WEB_SRC(baseSrc);
619     WebKitWebSrcPrivate* priv = src->priv;
620
621     if (priv->resource || (priv->loader && !priv->keepAlive))
622         webKitWebSrcCloseSession(src);
623
624     {
625         LockHolder adapterLocker(priv->adapterLock);
626         gst_adapter_clear(priv->adapter.get());
627         priv->queueSize = 0;
628     }
629
630     webkitWebSrcReset(src);
631     GST_DEBUG_OBJECT(src, "Stopped request");
632     return TRUE;
633 }
634
635 static gboolean webKitWebSrcGetSize(GstBaseSrc* baseSrc, guint64* size)
636 {
637     WebKitWebSrc* src = WEBKIT_WEB_SRC(baseSrc);
638     WebKitWebSrcPrivate* priv = src->priv;
639
640     GST_DEBUG_OBJECT(src, "haveSize: %s, size: %" G_GUINT64_FORMAT, boolForPrinting(priv->haveSize), priv->size);
641     if (priv->haveSize) {
642         *size = priv->size;
643         return TRUE;
644     }
645
646     return FALSE;
647 }
648
649 static gboolean webKitWebSrcIsSeekable(GstBaseSrc* baseSrc)
650 {
651     WebKitWebSrc* src = WEBKIT_WEB_SRC(baseSrc);
652
653     GST_DEBUG_OBJECT(src, "isSeekable: %s", boolForPrinting(src->priv->isSeekable));
654     return src->priv->isSeekable;
655 }
656
657 static gboolean webKitWebSrcDoSeek(GstBaseSrc* baseSrc, GstSegment* segment)
658 {
659     WebKitWebSrc* src = WEBKIT_WEB_SRC(baseSrc);
660     WebKitWebSrcPrivate* priv = src->priv;
661     LockHolder locker(priv->responseLock);
662
663     GST_DEBUG_OBJECT(src, "Seek segment: (%" G_GUINT64_FORMAT "-%" G_GUINT64_FORMAT ")", segment->start, segment->stop);
664     if (priv->readPosition == segment->start && priv->requestedPosition == priv->readPosition && priv->stopPosition == segment->stop) {
665         GST_DEBUG_OBJECT(src, "Seek to current read/end position and no seek pending");
666         return TRUE;
667     }
668
669     if (priv->wereHeadersReceived && !priv->isSeekable) {
670         GST_WARNING_OBJECT(src, "Not seekable");
671         return FALSE;
672     }
673
674     if (segment->rate < 0.0 || segment->format != GST_FORMAT_BYTES) {
675         GST_WARNING_OBJECT(src, "Invalid seek segment");
676         return FALSE;
677     }
678
679     if (priv->haveSize && segment->start >= priv->size)
680         GST_WARNING_OBJECT(src, "Potentially seeking behind end of file, might EOS immediately");
681
682     priv->isSeeking = true;
683     priv->requestedPosition = segment->start;
684     priv->stopPosition = segment->stop;
685     return TRUE;
686 }
687
688 static gboolean webKitWebSrcQuery(GstBaseSrc* baseSrc, GstQuery* query)
689 {
690     WebKitWebSrc* src = WEBKIT_WEB_SRC(baseSrc);
691     WebKitWebSrcPrivate* priv = src->priv;
692     gboolean result = FALSE;
693
694     if (GST_QUERY_TYPE(query) == GST_QUERY_URI) {
695         gst_query_set_uri(query, priv->originalURI.data());
696         if (!priv->redirectedURI.isNull())
697             gst_query_set_uri_redirection(query, priv->redirectedURI.data());
698         result = TRUE;
699     }
700
701     if (!result)
702         result = GST_BASE_SRC_CLASS(parent_class)->query(baseSrc, query);
703
704     if (GST_QUERY_TYPE(query) == GST_QUERY_SCHEDULING) {
705         GstSchedulingFlags flags;
706         int minSize, maxSize, align;
707
708         gst_query_parse_scheduling(query, &flags, &minSize, &maxSize, &align);
709         gst_query_set_scheduling(query, static_cast<GstSchedulingFlags>(flags | GST_SCHEDULING_FLAG_BANDWIDTH_LIMITED), minSize, maxSize, align);
710     }
711
712     return result;
713 }
714
715 static gboolean webKitWebSrcUnLock(GstBaseSrc* baseSrc)
716 {
717     WebKitWebSrc* src = WEBKIT_WEB_SRC(baseSrc);
718     LockHolder locker(src->priv->responseLock);
719
720     GST_DEBUG_OBJECT(src, "Unlock");
721     src->priv->isFlushing = true;
722     src->priv->responseCondition.notifyOne();
723     return TRUE;
724 }
725
726 static gboolean webKitWebSrcUnLockStop(GstBaseSrc* baseSrc)
727 {
728     WebKitWebSrc* src = WEBKIT_WEB_SRC(baseSrc);
729     LockHolder locker(src->priv->responseLock);
730     GST_DEBUG_OBJECT(src, "Unlock stop");
731     src->priv->isFlushing = false;
732
733     return TRUE;
734 }
735
736 static GstStateChangeReturn webKitWebSrcChangeState(GstElement* element, GstStateChange transition)
737 {
738     WebKitWebSrc* src = WEBKIT_WEB_SRC(element);
739
740 #if GST_CHECK_VERSION(1, 14, 0)
741     GST_DEBUG_OBJECT(src, gst_state_change_get_name(transition));
742 #endif
743     switch (transition) {
744     case GST_STATE_CHANGE_READY_TO_NULL:
745         webKitWebSrcCloseSession(src);
746         break;
747     default:
748         break;
749     }
750
751     return GST_ELEMENT_CLASS(parent_class)->change_state(element, transition);
752 }
753
754 static bool urlHasSupportedProtocol(const URL& url)
755 {
756     return url.isValid() && (url.protocolIsInHTTPFamily() || url.protocolIsBlob());
757 }
758
759 // uri handler interface
760
761 static GstURIType webKitWebSrcUriGetType(GType)
762 {
763     return GST_URI_SRC;
764 }
765
766 const gchar* const* webKitWebSrcGetProtocols(GType)
767 {
768     static const char* protocols[] = {"http", "https", "blob", nullptr };
769     return protocols;
770 }
771
772 static gchar* webKitWebSrcGetUri(GstURIHandler* handler)
773 {
774     WebKitWebSrc* src = WEBKIT_WEB_SRC(handler);
775     gchar* ret = g_strdup(src->priv->originalURI.data());
776     return ret;
777 }
778
779 static gboolean webKitWebSrcSetUri(GstURIHandler* handler, const gchar* uri, GError** error)
780 {
781     WebKitWebSrc* src = WEBKIT_WEB_SRC(handler);
782     WebKitWebSrcPrivate* priv = src->priv;
783
784     if (GST_STATE(src) >= GST_STATE_PAUSED) {
785         GST_ERROR_OBJECT(src, "URI can only be set in states < PAUSED");
786         return FALSE;
787     }
788
789     priv->redirectedURI = CString();
790     priv->originalURI = CString();
791     if (!uri)
792         return TRUE;
793
794     if (priv->originalURI.length()) {
795         GST_ERROR_OBJECT(src, "URI can only be set in states < PAUSED");
796         return FALSE;
797     }
798
799     URL url(URL(), uri);
800     if (!urlHasSupportedProtocol(url)) {
801         g_set_error(error, GST_URI_ERROR, GST_URI_ERROR_BAD_URI, "Invalid URI '%s'", uri);
802         return FALSE;
803     }
804
805     priv->originalURI = url.string().utf8();
806     return TRUE;
807 }
808
809 static void webKitWebSrcUriHandlerInit(gpointer gIface, gpointer)
810 {
811     GstURIHandlerInterface* iface = (GstURIHandlerInterface *) gIface;
812
813     iface->get_type = webKitWebSrcUriGetType;
814     iface->get_protocols = webKitWebSrcGetProtocols;
815     iface->get_uri = webKitWebSrcGetUri;
816     iface->set_uri = webKitWebSrcSetUri;
817 }
818
819 void webKitWebSrcSetMediaPlayer(WebKitWebSrc* src, WebCore::MediaPlayer* player)
820 {
821     ASSERT(player);
822     src->priv->player = player;
823 }
824
825 bool webKitSrcPassedCORSAccessCheck(WebKitWebSrc* src)
826 {
827     return src->priv->didPassAccessControlCheck;
828 }
829
830 CachedResourceStreamingClient::CachedResourceStreamingClient(WebKitWebSrc* src, ResourceRequest&& request)
831     : m_src(GST_ELEMENT(src))
832     , m_request(WTFMove(request))
833 {
834 }
835
836 CachedResourceStreamingClient::~CachedResourceStreamingClient() = default;
837
838 void CachedResourceStreamingClient::checkUpdateBlocksize(uint64_t bytesRead)
839 {
840     WebKitWebSrc* src = WEBKIT_WEB_SRC(m_src.get());
841     GstBaseSrc* baseSrc = GST_BASE_SRC_CAST(src);
842     WebKitWebSrcPrivate* priv = src->priv;
843
844     uint64_t blocksize = gst_base_src_get_blocksize(baseSrc);
845     GST_LOG_OBJECT(src, "Checking to update blocksize. Read: %" PRIu64 ", current blocksize: %" PRIu64, bytesRead, blocksize);
846
847     if (bytesRead >= blocksize * s_growBlocksizeLimit) {
848         m_reduceBlocksizeCount = 0;
849         m_increaseBlocksizeCount++;
850
851         if (m_increaseBlocksizeCount >= s_growBlocksizeCount) {
852             blocksize *= s_growBlocksizeFactor;
853             GST_DEBUG_OBJECT(src, "Increased blocksize to %" PRIu64, blocksize);
854             gst_base_src_set_blocksize(baseSrc, blocksize);
855             m_increaseBlocksizeCount = 0;
856         }
857     } else if (bytesRead < blocksize * s_reduceBlocksizeLimit) {
858         m_reduceBlocksizeCount++;
859         m_increaseBlocksizeCount = 0;
860
861         if (m_reduceBlocksizeCount >= s_reduceBlocksizeCount) {
862             blocksize *= s_reduceBlocksizeFactor;
863             blocksize = std::max(blocksize, priv->minimumBlocksize);
864             GST_DEBUG_OBJECT(src, "Decreased blocksize to %" PRIu64, blocksize);
865             gst_base_src_set_blocksize(baseSrc, blocksize);
866             m_reduceBlocksizeCount = 0;
867         }
868     } else {
869         m_reduceBlocksizeCount = 0;
870         m_increaseBlocksizeCount = 0;
871     }
872 }
873
874 void CachedResourceStreamingClient::responseReceived(PlatformMediaResource&, const ResourceResponse& response, CompletionHandler<void(ShouldContinue)>&& completionHandler)
875 {
876     WebKitWebSrc* src = WEBKIT_WEB_SRC(m_src.get());
877     WebKitWebSrcPrivate* priv = src->priv;
878     priv->didPassAccessControlCheck = priv->resource->didPassAccessControlCheck();
879
880     GST_DEBUG_OBJECT(src, "Received response: %d", response.httpStatusCode());
881
882     auto responseURI = response.url().string().utf8();
883     if (priv->originalURI != responseURI)
884         priv->redirectedURI = WTFMove(responseURI);
885
886     uint64_t length = response.expectedContentLength();
887     if (length > 0 && priv->requestedPosition && response.httpStatusCode() == 206)
888         length += priv->requestedPosition;
889
890     priv->httpHeaders.reset(gst_structure_new_empty("http-headers"));
891     gst_structure_set(priv->httpHeaders.get(), "uri", G_TYPE_STRING, priv->originalURI.data(),
892         "http-status-code", G_TYPE_UINT, response.httpStatusCode(), nullptr);
893     if (!priv->redirectedURI.isNull())
894         gst_structure_set(priv->httpHeaders.get(), "redirection-uri", G_TYPE_STRING, priv->redirectedURI.data(), nullptr);
895     GUniquePtr<GstStructure> headers(gst_structure_new_empty("request-headers"));
896     for (const auto& header : m_request.httpHeaderFields())
897         gst_structure_set(headers.get(), header.key.utf8().data(), G_TYPE_STRING, header.value.utf8().data(), nullptr);
898     GST_DEBUG_OBJECT(src, "Request headers going downstream: %" GST_PTR_FORMAT, headers.get());
899     gst_structure_set(priv->httpHeaders.get(), "request-headers", GST_TYPE_STRUCTURE, headers.get(), nullptr);
900     headers.reset(gst_structure_new_empty("response-headers"));
901     for (const auto& header : response.httpHeaderFields()) {
902         bool ok = false;
903         uint64_t convertedValue = header.value.toUInt64(&ok);
904         if (ok)
905             gst_structure_set(headers.get(), header.key.utf8().data(), G_TYPE_UINT64, convertedValue, nullptr);
906         else
907             gst_structure_set(headers.get(), header.key.utf8().data(), G_TYPE_STRING, header.value.utf8().data(), nullptr);
908     }
909     auto contentLengthFieldName(httpHeaderNameString(HTTPHeaderName::ContentLength).toString());
910     if (!gst_structure_has_field(headers.get(), contentLengthFieldName.utf8().data()))
911         gst_structure_set(headers.get(), contentLengthFieldName.utf8().data(), G_TYPE_UINT64, static_cast<uint64_t>(length), nullptr);
912     gst_structure_set(priv->httpHeaders.get(), "response-headers", GST_TYPE_STRUCTURE, headers.get(), nullptr);
913     GST_DEBUG_OBJECT(src, "Response headers going downstream: %" GST_PTR_FORMAT, headers.get());
914
915     priv->httpHeadersEvent = adoptGRef(gst_event_new_custom(GST_EVENT_CUSTOM_DOWNSTREAM_STICKY, gst_structure_copy(priv->httpHeaders.get())));
916
917     auto scopeExit = makeScopeExit([&] {
918         GstStructure* structure = gst_structure_copy(src->priv->httpHeaders.get());
919         gst_element_post_message(GST_ELEMENT_CAST(src), gst_message_new_element(GST_OBJECT_CAST(src), structure));
920     });
921
922     if (response.httpStatusCode() >= 400) {
923         GST_ELEMENT_ERROR(src, RESOURCE, READ, ("Received %d HTTP error code", response.httpStatusCode()), (nullptr));
924         priv->doesHaveEOS = true;
925         webKitWebSrcStop(GST_BASE_SRC_CAST(src));
926         completionHandler(ShouldContinue::No);
927         return;
928     }
929
930     if (priv->requestedPosition) {
931         // Seeking ... we expect a 206 == PARTIAL_CONTENT
932         if (response.httpStatusCode() == 200) {
933             // Range request didn't have a ranged response; resetting offset.
934             priv->readPosition = 0;
935         } else if (response.httpStatusCode() != 206) {
936             // Range request completely failed.
937             GST_ELEMENT_ERROR(src, RESOURCE, READ, ("Received unexpected %d HTTP status code", response.httpStatusCode()), (nullptr));
938             priv->doesHaveEOS = true;
939             webKitWebSrcStop(GST_BASE_SRC_CAST(src));
940             completionHandler(ShouldContinue::No);
941             return;
942         } else {
943             GST_DEBUG_OBJECT(src, "Range request succeeded");
944             priv->isSeeking = false;
945             priv->wasSeeking = true;
946         }
947     }
948
949     priv->isSeekable = length > 0 && g_ascii_strcasecmp("none", response.httpHeaderField(HTTPHeaderName::AcceptRanges).utf8().data());
950
951     GST_DEBUG_OBJECT(src, "Size: %" G_GUINT64_FORMAT ", isSeekable: %s", length, boolForPrinting(priv->isSeekable));
952     if (length > 0) {
953         if (!priv->haveSize || priv->size != length) {
954             priv->haveSize = true;
955             priv->size = length;
956             priv->isDurationSet = false;
957         }
958     }
959
960     // Signal to downstream if this is an Icecast stream.
961     GRefPtr<GstCaps> caps;
962     String metadataIntervalAsString = response.httpHeaderField(HTTPHeaderName::IcyMetaInt);
963     if (!metadataIntervalAsString.isEmpty()) {
964         bool isMetadataIntervalParsed;
965         int metadataInterval = metadataIntervalAsString.toInt(&isMetadataIntervalParsed);
966         if (isMetadataIntervalParsed && metadataInterval > 0) {
967             caps = adoptGRef(gst_caps_new_simple("application/x-icy", "metadata-interval", G_TYPE_INT, metadataInterval, nullptr));
968
969             String contentType = response.httpHeaderField(HTTPHeaderName::ContentType);
970             GST_DEBUG_OBJECT(src, "Response ContentType: %s", contentType.utf8().data());
971             gst_caps_set_simple(caps.get(), "content-type", G_TYPE_STRING, contentType.utf8().data(), nullptr);
972         }
973     }
974
975     if (caps) {
976         GST_DEBUG_OBJECT(src, "Set caps to %" GST_PTR_FORMAT, caps.get());
977         gst_base_src_set_caps(GST_BASE_SRC_CAST(src), caps.get());
978     }
979
980     {
981         LockHolder locker(priv->responseLock);
982         priv->wereHeadersReceived = true;
983         priv->headersCondition.notifyOne();
984     }
985     completionHandler(ShouldContinue::Yes);
986 }
987
988 void CachedResourceStreamingClient::dataReceived(PlatformMediaResource&, const char* data, int length)
989 {
990     WebKitWebSrc* src = WEBKIT_WEB_SRC(m_src.get());
991     GstBaseSrc* baseSrc = GST_BASE_SRC_CAST(src);
992     WebKitWebSrcPrivate* priv = src->priv;
993
994     GST_LOG_OBJECT(src, "Have %d bytes of data", length);
995     LockHolder locker(priv->responseLock);
996
997     uint64_t newPosition = priv->readPosition + length;
998     if (LIKELY (priv->requestedPosition == priv->readPosition))
999         priv->requestedPosition = newPosition;
1000     priv->readPosition = newPosition;
1001
1002     uint64_t newSize = 0;
1003     if (priv->haveSize && (newPosition > priv->size)) {
1004         GST_DEBUG_OBJECT(src, "Got position previous estimated content size (%" G_GINT64_FORMAT " > %" G_GINT64_FORMAT ")", newPosition, priv->size);
1005         newSize = newPosition;
1006     } else if (!priv->haveSize) {
1007         GST_DEBUG_OBJECT(src, "Got initial response without Content-Length, assuming response size as duration.");
1008         newSize = length;
1009         priv->haveSize = true;
1010     }
1011
1012     if (newSize) {
1013         priv->size = newSize;
1014         baseSrc->segment.duration = priv->size;
1015         gst_element_post_message(GST_ELEMENT_CAST(src), gst_message_new_duration_changed(GST_OBJECT_CAST(src)));
1016     }
1017
1018     gst_element_post_message(GST_ELEMENT_CAST(src), gst_message_new_element(GST_OBJECT_CAST(src),
1019         gst_structure_new("webkit-network-statistics", "read-position", G_TYPE_UINT64, priv->readPosition, "size", G_TYPE_UINT64, priv->size, nullptr)));
1020
1021     checkUpdateBlocksize(length);
1022
1023     if (!priv->wasResponseReceived)
1024         priv->wasResponseReceived = true;
1025     priv->responseCondition.notifyOne();
1026
1027     {
1028         LockHolder adapterLocker(priv->adapterLock);
1029         GstBuffer* buffer = gst_buffer_new_wrapped(g_memdup(data, length), length);
1030         priv->queueSize += length;
1031         gst_adapter_push(priv->adapter.get(), buffer);
1032         priv->adapterCondition.notifyOne();
1033     }
1034 }
1035
1036 void CachedResourceStreamingClient::accessControlCheckFailed(PlatformMediaResource&, const ResourceError& error)
1037 {
1038     WebKitWebSrc* src = WEBKIT_WEB_SRC(m_src.get());
1039     GST_ELEMENT_ERROR(src, RESOURCE, READ, ("%s", error.localizedDescription().utf8().data()), (nullptr));
1040     src->priv->doesHaveEOS = true;
1041     webKitWebSrcStop(GST_BASE_SRC_CAST(src));
1042 }
1043
1044 void CachedResourceStreamingClient::loadFailed(PlatformMediaResource&, const ResourceError& error)
1045 {
1046     WebKitWebSrc* src = WEBKIT_WEB_SRC(m_src.get());
1047
1048     if (!error.isCancellation()) {
1049         GST_ERROR_OBJECT(src, "Have failure: %s", error.localizedDescription().utf8().data());
1050         GST_ELEMENT_ERROR(src, RESOURCE, FAILED, ("%s", error.localizedDescription().utf8().data()), (nullptr));
1051     }
1052
1053     src->priv->doesHaveEOS = true;
1054 }
1055
1056 void CachedResourceStreamingClient::loadFinished(PlatformMediaResource&)
1057 {
1058     WebKitWebSrc* src = WEBKIT_WEB_SRC(m_src.get());
1059     WebKitWebSrcPrivate* priv = src->priv;
1060
1061     if (priv->isSeeking && !priv->isFlushing)
1062         priv->isSeeking = false;
1063 }
1064
1065 #endif // ENABLE(VIDEO) && USE(GSTREAMER)