diff options
author | Lorry Tar Creator <lorry-tar-importer@lorry> | 2017-06-27 06:07:23 +0000 |
---|---|---|
committer | Lorry Tar Creator <lorry-tar-importer@lorry> | 2017-06-27 06:07:23 +0000 |
commit | 1bf1084f2b10c3b47fd1a588d85d21ed0eb41d0c (patch) | |
tree | 46dcd36c86e7fbc6e5df36deb463b33e9967a6f7 /Source/WebCore/platform/graphics/gstreamer/WebKitWebSourceGStreamer.cpp | |
parent | 32761a6cee1d0dee366b885b7b9c777e67885688 (diff) | |
download | WebKitGtk-tarball-master.tar.gz |
webkitgtk-2.16.5HEADwebkitgtk-2.16.5master
Diffstat (limited to 'Source/WebCore/platform/graphics/gstreamer/WebKitWebSourceGStreamer.cpp')
-rw-r--r-- | Source/WebCore/platform/graphics/gstreamer/WebKitWebSourceGStreamer.cpp | 1083 |
1 files changed, 565 insertions, 518 deletions
diff --git a/Source/WebCore/platform/graphics/gstreamer/WebKitWebSourceGStreamer.cpp b/Source/WebCore/platform/graphics/gstreamer/WebKitWebSourceGStreamer.cpp index c7d8eca76..1b31b380b 100644 --- a/Source/WebCore/platform/graphics/gstreamer/WebKitWebSourceGStreamer.cpp +++ b/Source/WebCore/platform/graphics/gstreamer/WebKitWebSourceGStreamer.cpp @@ -22,144 +22,160 @@ #if ENABLE(VIDEO) && USE(GSTREAMER) -#include "CachedRawResource.h" -#include "CachedRawResourceClient.h" -#include "CachedResourceHandle.h" -#include "CachedResourceLoader.h" -#include "CachedResourceRequest.h" -#include "CrossOriginAccessControl.h" #include "GRefPtrGStreamer.h" #include "GStreamerUtilities.h" +#include "GUniquePtrGStreamer.h" +#include "HTTPHeaderNames.h" +#include "MainThreadNotifier.h" #include "MediaPlayer.h" #include "NotImplemented.h" +#include "PlatformMediaResourceLoader.h" +#include "ResourceError.h" #include "ResourceHandle.h" #include "ResourceHandleClient.h" #include "ResourceRequest.h" #include "ResourceResponse.h" -#include "SecurityOrigin.h" #include "SharedBuffer.h" #include <gst/app/gstappsrc.h> #include <gst/gst.h> #include <gst/pbutils/missing-plugins.h> +#include <wtf/MainThread.h> #include <wtf/Noncopyable.h> -#include <wtf/gobject/GMutexLocker.h> -#include <wtf/gobject/GRefPtr.h> -#include <wtf/gobject/GUniquePtr.h> +#include <wtf/glib/GMutexLocker.h> +#include <wtf/glib/GRefPtr.h> +#include <wtf/glib/GUniquePtr.h> #include <wtf/text/CString.h> +#if USE(SOUP) +#include "SoupNetworkSession.h" +#endif + using namespace WebCore; -enum CORSAccessCheckResult { - CORSNoCheck, - CORSSuccess, - CORSFailure +class StreamingClient { +public: + StreamingClient(WebKitWebSrc*, ResourceRequest&&); + virtual ~StreamingClient(); + +protected: + char* createReadBuffer(size_t requestedSize, size_t& actualSize); + void handleResponseReceived(const ResourceResponse&); + void handleDataReceived(const char*, int); + void handleNotifyFinished(); + + GRefPtr<GstElement> m_src; + ResourceRequest m_request; }; -class StreamingClient { - public: - StreamingClient(WebKitWebSrc*); - virtual ~StreamingClient(); +class CachedResourceStreamingClient final : public PlatformMediaResourceClient, public StreamingClient { + WTF_MAKE_NONCOPYABLE(CachedResourceStreamingClient); +public: + CachedResourceStreamingClient(WebKitWebSrc*, ResourceRequest&&); + virtual ~CachedResourceStreamingClient(); + +private: + // PlatformMediaResourceClient virtual methods. +#if USE(SOUP) + char* getOrCreateReadBuffer(PlatformMediaResource&, size_t requestedSize, size_t& actualSize) override; +#endif + void responseReceived(PlatformMediaResource&, const ResourceResponse&) override; + void dataReceived(PlatformMediaResource&, const char*, int) override; + void accessControlCheckFailed(PlatformMediaResource&, const ResourceError&) override; + void loadFailed(PlatformMediaResource&, const ResourceError&) override; + void loadFinished(PlatformMediaResource&) override; +}; - virtual bool loadFailed() const = 0; - virtual void setDefersLoading(bool) = 0; +class ResourceHandleStreamingClient : public ThreadSafeRefCounted<ResourceHandleStreamingClient>, public ResourceHandleClient, public StreamingClient { +public: + static Ref<ResourceHandleStreamingClient> create(WebKitWebSrc* src, ResourceRequest&& request) + { + return adoptRef(*new ResourceHandleStreamingClient(src, WTFMove(request))); + } + virtual ~ResourceHandleStreamingClient(); - protected: - char* createReadBuffer(size_t requestedSize, size_t& actualSize); - void handleResponseReceived(const ResourceResponse&, CORSAccessCheckResult); - void handleDataReceived(const char*, int); - void handleNotifyFinished(); + void invalidate(); - GstElement* m_src; -}; + // StreamingClient virtual methods. + bool loadFailed() const; + void setDefersLoading(bool); -class CachedResourceStreamingClient : public CachedRawResourceClient, public StreamingClient { - WTF_MAKE_NONCOPYABLE(CachedResourceStreamingClient); WTF_MAKE_FAST_ALLOCATED; - public: - CachedResourceStreamingClient(WebKitWebSrc*, CachedResourceLoader*, const ResourceRequest&, MediaPlayerClient::CORSMode); - virtual ~CachedResourceStreamingClient(); - - // StreamingClient virtual methods. - virtual bool loadFailed() const; - virtual void setDefersLoading(bool); - - private: - // CachedResourceClient virtual methods. - virtual char* getOrCreateReadBuffer(CachedResource*, size_t requestedSize, size_t& actualSize); - virtual void responseReceived(CachedResource*, const ResourceResponse&); - virtual void dataReceived(CachedResource*, const char*, int); - virtual void notifyFinished(CachedResource*); - - CachedResourceHandle<CachedRawResource> m_resource; - RefPtr<SecurityOrigin> m_origin; +private: + ResourceHandleStreamingClient(WebKitWebSrc*, ResourceRequest&&); + void cleanupAndStopRunLoop(); + + // ResourceHandleClient virtual methods. +#if USE(SOUP) + char* getOrCreateReadBuffer(size_t requestedSize, size_t& actualSize) override; +#endif + ResourceRequest willSendRequest(ResourceHandle*, ResourceRequest&&, ResourceResponse&&) override; + void didReceiveResponse(ResourceHandle*, ResourceResponse&&) override; + void didReceiveData(ResourceHandle*, const char*, unsigned, int) override; + void didReceiveBuffer(ResourceHandle*, Ref<SharedBuffer>&&, int encodedLength) override; + void didFinishLoading(ResourceHandle*, double) override; + void didFail(ResourceHandle*, const ResourceError&) override; + void wasBlocked(ResourceHandle*) override; + void cannotShowURL(ResourceHandle*) override; + + ThreadIdentifier m_thread { 0 }; + Lock m_initializeRunLoopConditionMutex; + Condition m_initializeRunLoopCondition; + RunLoop* m_runLoop { nullptr }; + Lock m_terminateRunLoopConditionMutex; + Condition m_terminateRunLoopCondition; + RefPtr<ResourceHandle> m_resource; +#if USE(SOUP) + std::unique_ptr<SoupNetworkSession> m_session; +#endif }; -class ResourceHandleStreamingClient : public ResourceHandleClient, public StreamingClient { - WTF_MAKE_NONCOPYABLE(ResourceHandleStreamingClient); WTF_MAKE_FAST_ALLOCATED; - public: - ResourceHandleStreamingClient(WebKitWebSrc*, const ResourceRequest&); - virtual ~ResourceHandleStreamingClient(); - - // StreamingClient virtual methods. - virtual bool loadFailed() const; - virtual void setDefersLoading(bool); - - private: - // ResourceHandleClient virtual methods. - virtual char* getOrCreateReadBuffer(size_t requestedSize, size_t& actualSize); - virtual void willSendRequest(ResourceHandle*, ResourceRequest&, const ResourceResponse&); - virtual void didReceiveResponse(ResourceHandle*, const ResourceResponse&); - virtual void didReceiveData(ResourceHandle*, const char*, unsigned, int); - virtual void didReceiveBuffer(ResourceHandle*, PassRefPtr<SharedBuffer>, int encodedLength); - virtual void didFinishLoading(ResourceHandle*, double /*finishTime*/); - virtual void didFail(ResourceHandle*, const ResourceError&); - virtual void wasBlocked(ResourceHandle*); - virtual void cannotShowURL(ResourceHandle*); - - RefPtr<ResourceHandle> m_resource; +enum MainThreadSourceNotification { + Start = 1 << 0, + Stop = 1 << 1, + NeedData = 1 << 2, + EnoughData = 1 << 3, + Seek = 1 << 4 }; #define WEBKIT_WEB_SRC_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE((obj), WEBKIT_TYPE_WEB_SRC, WebKitWebSrcPrivate)) struct _WebKitWebSrcPrivate { GstAppSrc* appsrc; GstPad* srcpad; - gchar* uri; + CString originalURI; + CString redirectedURI; + bool keepAlive; + GUniquePtr<GstStructure> extraHeaders; + bool compress; + GUniquePtr<gchar> httpMethod; WebCore::MediaPlayer* player; - StreamingClient* client; + RefPtr<PlatformMediaResourceLoader> loader; + RefPtr<PlatformMediaResource> resource; + RefPtr<ResourceHandleStreamingClient> client; - CORSAccessCheckResult corsAccessCheck; + bool didPassAccessControlCheck; guint64 offset; guint64 size; gboolean seekable; - gboolean paused; + bool paused; + bool isSeeking; guint64 requestedOffset; - guint startID; - guint stopID; - guint needDataID; - guint enoughDataID; - guint seekID; - + bool createdInMainThread; + RefPtr<MainThreadNotifier<MainThreadSourceNotification>> notifier; GRefPtr<GstBuffer> buffer; - - // icecast stuff - gboolean iradioMode; - gchar* iradioName; - gchar* iradioGenre; - gchar* iradioUrl; - gchar* iradioTitle; }; enum { - PROP_IRADIO_MODE = 1, - PROP_IRADIO_NAME, - PROP_IRADIO_GENRE, - PROP_IRADIO_URL, - PROP_IRADIO_TITLE, - PROP_LOCATION + PROP_0, + PROP_LOCATION, + PROP_RESOLVED_LOCATION, + PROP_KEEP_ALIVE, + PROP_EXTRA_HEADERS, + PROP_COMPRESS, + PROP_METHOD }; static GstStaticPadTemplate srcTemplate = GST_STATIC_PAD_TEMPLATE("src", @@ -180,15 +196,24 @@ static GstStateChangeReturn webKitWebSrcChangeState(GstElement*, GstStateChange) static gboolean webKitWebSrcQueryWithParent(GstPad*, GstObject*, GstQuery*); -static void webKitWebSrcNeedDataCb(GstAppSrc*, guint length, gpointer userData); -static void webKitWebSrcEnoughDataCb(GstAppSrc*, gpointer userData); -static gboolean webKitWebSrcSeekDataCb(GstAppSrc*, guint64 offset, gpointer userData); +static void webKitWebSrcNeedData(WebKitWebSrc*); +static void webKitWebSrcEnoughData(WebKitWebSrc*); +static gboolean webKitWebSrcSeek(WebKitWebSrc*, guint64); static GstAppSrcCallbacks appsrcCallbacks = { - webKitWebSrcNeedDataCb, - webKitWebSrcEnoughDataCb, - webKitWebSrcSeekDataCb, - { 0 } + // need_data + [](GstAppSrc*, guint, gpointer userData) { + webKitWebSrcNeedData(WEBKIT_WEB_SRC(userData)); + }, + // enough_data + [](GstAppSrc*, gpointer userData) { + webKitWebSrcEnoughData(WEBKIT_WEB_SRC(userData)); + }, + // seek_data + [](GstAppSrc*, guint64 offset, gpointer userData) -> gboolean { + return webKitWebSrcSeek(WEBKIT_WEB_SRC(userData), offset); + }, + { nullptr } }; #define webkit_web_src_parent_class parent_class @@ -213,57 +238,32 @@ static void webkit_web_src_class_init(WebKitWebSrcClass* klass) gst_element_class_set_metadata(eklass, "WebKit Web source element", "Source", "Handles HTTP/HTTPS uris", "Sebastian Dröge <sebastian.droege@collabora.co.uk>"); - // icecast stuff - g_object_class_install_property(oklass, - PROP_IRADIO_MODE, - g_param_spec_boolean("iradio-mode", - "iradio-mode", - "Enable internet radio mode (extraction of shoutcast/icecast metadata)", - FALSE, - (GParamFlags) (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS))); - - g_object_class_install_property(oklass, - PROP_IRADIO_NAME, - g_param_spec_string("iradio-name", - "iradio-name", - "Name of the stream", - 0, - (GParamFlags) (G_PARAM_READABLE | G_PARAM_STATIC_STRINGS))); - - g_object_class_install_property(oklass, - PROP_IRADIO_GENRE, - g_param_spec_string("iradio-genre", - "iradio-genre", - "Genre of the stream", - 0, - (GParamFlags) (G_PARAM_READABLE | G_PARAM_STATIC_STRINGS))); - - g_object_class_install_property(oklass, - PROP_IRADIO_URL, - g_param_spec_string("iradio-url", - "iradio-url", - "Homepage URL for radio stream", - 0, - (GParamFlags) (G_PARAM_READABLE | G_PARAM_STATIC_STRINGS))); - - g_object_class_install_property(oklass, - PROP_IRADIO_TITLE, - g_param_spec_string("iradio-title", - "iradio-title", - "Name of currently playing song", - 0, - (GParamFlags) (G_PARAM_READABLE | G_PARAM_STATIC_STRINGS))); - - /* Allows setting the uri using the 'location' property, which is used * for example by gst_element_make_from_uri() */ - g_object_class_install_property(oklass, - PROP_LOCATION, - g_param_spec_string("location", - "location", - "Location to read from", - 0, - (GParamFlags) (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS))); + g_object_class_install_property(oklass, PROP_LOCATION, + g_param_spec_string("location", "location", "Location to read from", + nullptr, static_cast<GParamFlags>(G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS))); + + g_object_class_install_property(oklass, PROP_RESOLVED_LOCATION, + g_param_spec_string("resolved-location", "Resolved location", "The location resolved by the server", + nullptr, static_cast<GParamFlags>(G_PARAM_READABLE | G_PARAM_STATIC_STRINGS))); + + g_object_class_install_property(oklass, PROP_KEEP_ALIVE, + g_param_spec_boolean("keep-alive", "keep-alive", "Use HTTP persistent connections", + FALSE, static_cast<GParamFlags>(G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS))); + + g_object_class_install_property(oklass, PROP_EXTRA_HEADERS, + g_param_spec_boxed("extra-headers", "Extra Headers", "Extra headers to append to the HTTP request", + GST_TYPE_STRUCTURE, static_cast<GParamFlags>(G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS))); + + g_object_class_install_property(oklass, PROP_COMPRESS, + g_param_spec_boolean("compress", "Compress", "Allow compressed content encodings", + FALSE, static_cast<GParamFlags>(G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS))); + + g_object_class_install_property(oklass, PROP_METHOD, + g_param_spec_string("method", "method", "The HTTP method to use (default: GET)", + nullptr, static_cast<GParamFlags>(G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS))); + eklass->change_state = webKitWebSrcChangeState; g_type_class_add_private(klass, sizeof(WebKitWebSrcPrivate)); @@ -274,8 +274,12 @@ static void webkit_web_src_init(WebKitWebSrc* src) WebKitWebSrcPrivate* priv = WEBKIT_WEB_SRC_GET_PRIVATE(src); src->priv = priv; + new (priv) WebKitWebSrcPrivate(); + + priv->createdInMainThread = isMainThread(); + priv->notifier = MainThreadNotifier<MainThreadSourceNotification>::create(); - priv->appsrc = GST_APP_SRC(gst_element_factory_make("appsrc", 0)); + priv->appsrc = GST_APP_SRC(gst_element_factory_make("appsrc", nullptr)); if (!priv->appsrc) { GST_ERROR_OBJECT(src, "Failed to create appsrc"); return; @@ -292,7 +296,7 @@ static void webkit_web_src_init(WebKitWebSrc* src) GST_OBJECT_FLAG_SET(priv->srcpad, GST_PAD_FLAG_NEED_PARENT); gst_pad_set_query_function(priv->srcpad, webKitWebSrcQueryWithParent); - gst_app_src_set_callbacks(priv->appsrc, &appsrcCallbacks, src, 0); + gst_app_src_set_callbacks(priv->appsrc, &appsrcCallbacks, src, nullptr); gst_app_src_set_emit_signals(priv->appsrc, FALSE); gst_app_src_set_stream_type(priv->appsrc, GST_APP_STREAM_TYPE_SEEKABLE); @@ -313,28 +317,32 @@ static void webkit_web_src_init(WebKitWebSrc* src) // likely that libsoup already provides new data before // the queue is really empty. // This might need tweaking for ports not using libsoup. - g_object_set(priv->appsrc, "min-percent", 20, NULL); + g_object_set(priv->appsrc, "min-percent", 20, nullptr); - gst_app_src_set_caps(priv->appsrc, 0); + gst_base_src_set_automatic_eos(GST_BASE_SRC(priv->appsrc), FALSE); + + gst_app_src_set_caps(priv->appsrc, nullptr); gst_app_src_set_size(priv->appsrc, -1); } static void webKitWebSrcDispose(GObject* object) { - WebKitWebSrc* src = WEBKIT_WEB_SRC(object); - WebKitWebSrcPrivate* priv = src->priv; + WebKitWebSrcPrivate* priv = WEBKIT_WEB_SRC(object)->priv; + if (priv->notifier) { + priv->notifier->invalidate(); + priv->notifier = nullptr; + } - priv->player = 0; + priv->player = nullptr; GST_CALL_PARENT(G_OBJECT_CLASS, dispose, (object)); } static void webKitWebSrcFinalize(GObject* object) { - WebKitWebSrc* src = WEBKIT_WEB_SRC(object); - WebKitWebSrcPrivate* priv = src->priv; + WebKitWebSrcPrivate* priv = WEBKIT_WEB_SRC(object)->priv; - g_free(priv->uri); + priv->~WebKitWebSrcPrivate(); GST_CALL_PARENT(G_OBJECT_CLASS, finalize, (object)); } @@ -342,16 +350,24 @@ static void webKitWebSrcFinalize(GObject* object) static void webKitWebSrcSetProperty(GObject* object, guint propID, const GValue* value, GParamSpec* pspec) { WebKitWebSrc* src = WEBKIT_WEB_SRC(object); - WebKitWebSrcPrivate* priv = src->priv; switch (propID) { - case PROP_IRADIO_MODE: { - WTF::GMutexLocker locker(GST_OBJECT_GET_LOCK(src)); - priv->iradioMode = g_value_get_boolean(value); + case PROP_LOCATION: + gst_uri_handler_set_uri(reinterpret_cast<GstURIHandler*>(src), g_value_get_string(value), nullptr); + break; + case PROP_KEEP_ALIVE: + src->priv->keepAlive = g_value_get_boolean(value); + break; + case PROP_EXTRA_HEADERS: { + const GstStructure* s = gst_value_get_structure(value); + src->priv->extraHeaders.reset(s ? gst_structure_copy(s) : nullptr); break; } - case PROP_LOCATION: - gst_uri_handler_set_uri(reinterpret_cast<GstURIHandler*>(src), g_value_get_string(value), 0); + case PROP_COMPRESS: + src->priv->compress = g_value_get_boolean(value); + break; + case PROP_METHOD: + src->priv->httpMethod.reset(g_value_dup_string(value)); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID(object, propID, pspec); @@ -364,25 +380,25 @@ static void webKitWebSrcGetProperty(GObject* object, guint propID, GValue* value WebKitWebSrc* src = WEBKIT_WEB_SRC(object); WebKitWebSrcPrivate* priv = src->priv; - WTF::GMutexLocker locker(GST_OBJECT_GET_LOCK(src)); + WTF::GMutexLocker<GMutex> locker(*GST_OBJECT_GET_LOCK(src)); switch (propID) { - case PROP_IRADIO_MODE: - g_value_set_boolean(value, priv->iradioMode); + case PROP_LOCATION: + g_value_set_string(value, priv->originalURI.data()); break; - case PROP_IRADIO_NAME: - g_value_set_string(value, priv->iradioName); + case PROP_RESOLVED_LOCATION: + g_value_set_string(value, priv->redirectedURI.isNull() ? priv->originalURI.data() : priv->redirectedURI.data()); break; - case PROP_IRADIO_GENRE: - g_value_set_string(value, priv->iradioGenre); + case PROP_KEEP_ALIVE: + g_value_set_boolean(value, priv->keepAlive); break; - case PROP_IRADIO_URL: - g_value_set_string(value, priv->iradioUrl); + case PROP_EXTRA_HEADERS: + gst_value_set_structure(value, priv->extraHeaders.get()); break; - case PROP_IRADIO_TITLE: - g_value_set_string(value, priv->iradioTitle); + case PROP_COMPRESS: + g_value_set_boolean(value, priv->compress); break; - case PROP_LOCATION: - g_value_set_string(value, priv->uri); + case PROP_METHOD: + g_value_set_string(value, priv->httpMethod.get()); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID(object, propID, pspec); @@ -390,123 +406,159 @@ static void webKitWebSrcGetProperty(GObject* object, guint propID, GValue* value } } -static void removeTimeoutSources(WebKitWebSrc* src) +static void webKitWebSrcStop(WebKitWebSrc* src) { WebKitWebSrcPrivate* priv = src->priv; - if (priv->startID) - g_source_remove(priv->startID); - priv->startID = 0; - - if (priv->needDataID) - g_source_remove(priv->needDataID); - priv->needDataID = 0; - - if (priv->enoughDataID) - g_source_remove(priv->enoughDataID); - priv->enoughDataID = 0; - - if (priv->seekID) - g_source_remove(priv->seekID); - priv->seekID = 0; -} - -static gboolean webKitWebSrcStop(WebKitWebSrc* src) -{ - WebKitWebSrcPrivate* priv = src->priv; - - ASSERT(isMainThread()); - - WTF::GMutexLocker locker(GST_OBJECT_GET_LOCK(src)); - - bool seeking = priv->seekID; - - removeTimeoutSources(src); - priv->stopID = 0; + if (priv->resource || (priv->loader && !priv->keepAlive)) { + GRefPtr<WebKitWebSrc> protector = WTF::ensureGRef(src); + priv->notifier->cancelPendingNotifications(MainThreadSourceNotification::NeedData | MainThreadSourceNotification::EnoughData | MainThreadSourceNotification::Seek); + priv->notifier->notify(MainThreadSourceNotification::Stop, [protector, keepAlive = priv->keepAlive] { + WebKitWebSrcPrivate* priv = protector->priv; + + WTF::GMutexLocker<GMutex> locker(*GST_OBJECT_GET_LOCK(protector.get())); + if (priv->resource) { + priv->resource->stop(); + priv->resource->setClient(nullptr); + priv->resource = nullptr; + } + + if (!keepAlive) + priv->loader = nullptr; + }); + } if (priv->client) { - delete priv->client; - priv->client = 0; + priv->client->invalidate(); + priv->client = nullptr; } + WTF::GMutexLocker<GMutex> locker(*GST_OBJECT_GET_LOCK(src)); + + bool wasSeeking = std::exchange(priv->isSeeking, false); + if (priv->buffer) { unmapGstBuffer(priv->buffer.get()); priv->buffer.clear(); } - priv->paused = FALSE; - - g_free(priv->iradioName); - priv->iradioName = 0; - - g_free(priv->iradioGenre); - priv->iradioGenre = 0; - - g_free(priv->iradioUrl); - priv->iradioUrl = 0; - - g_free(priv->iradioTitle); - priv->iradioTitle = 0; + priv->paused = false; priv->offset = 0; priv->seekable = FALSE; - if (!seeking) { + if (!wasSeeking) { priv->size = 0; priv->requestedOffset = 0; - priv->player = 0; + priv->player = nullptr; } locker.unlock(); if (priv->appsrc) { - gst_app_src_set_caps(priv->appsrc, 0); - if (!seeking) + gst_app_src_set_caps(priv->appsrc, nullptr); + if (!wasSeeking) gst_app_src_set_size(priv->appsrc, -1); } GST_DEBUG_OBJECT(src, "Stopped request"); +} + +static bool webKitWebSrcSetExtraHeader(GQuark fieldId, const GValue* value, gpointer userData) +{ + GUniquePtr<gchar> fieldContent; + + if (G_VALUE_HOLDS_STRING(value)) + fieldContent.reset(g_value_dup_string(value)); + else { + GValue dest = G_VALUE_INIT; + + g_value_init(&dest, G_TYPE_STRING); + if (g_value_transform(value, &dest)) + fieldContent.reset(g_value_dup_string(&dest)); + } + + const gchar* fieldName = g_quark_to_string(fieldId); + if (!fieldContent.get()) { + GST_ERROR("extra-headers field '%s' contains no value or can't be converted to a string", fieldName); + return false; + } - return FALSE; + GST_DEBUG("Appending extra header: \"%s: %s\"", fieldName, fieldContent.get()); + ResourceRequest* request = static_cast<ResourceRequest*>(userData); + request->setHTTPHeaderField(fieldName, fieldContent.get()); + return true; } -static gboolean webKitWebSrcStart(WebKitWebSrc* src) +static gboolean webKitWebSrcProcessExtraHeaders(GQuark fieldId, const GValue* value, gpointer userData) { - WebKitWebSrcPrivate* priv = src->priv; + if (G_VALUE_TYPE(value) == GST_TYPE_ARRAY) { + unsigned size = gst_value_array_get_size(value); - ASSERT(isMainThread()); + for (unsigned i = 0; i < size; i++) { + if (!webKitWebSrcSetExtraHeader(fieldId, gst_value_array_get_value(value, i), userData)) + return FALSE; + } + return TRUE; + } - WTF::GMutexLocker locker(GST_OBJECT_GET_LOCK(src)); + if (G_VALUE_TYPE(value) == GST_TYPE_LIST) { + unsigned size = gst_value_list_get_size(value); + + for (unsigned i = 0; i < size; i++) { + if (!webKitWebSrcSetExtraHeader(fieldId, gst_value_list_get_value(value, i), userData)) + return FALSE; + } + return TRUE; + } + + return webKitWebSrcSetExtraHeader(fieldId, value, userData); +} + +static void webKitWebSrcStart(WebKitWebSrc* src) +{ + WebKitWebSrcPrivate* priv = src->priv; - priv->startID = 0; - priv->corsAccessCheck = CORSNoCheck; + WTF::GMutexLocker<GMutex> locker(*GST_OBJECT_GET_LOCK(src)); - if (!priv->uri) { + priv->didPassAccessControlCheck = false; + + if (priv->originalURI.isNull()) { GST_ERROR_OBJECT(src, "No URI provided"); locker.unlock(); webKitWebSrcStop(src); - return FALSE; + return; } ASSERT(!priv->client); - URL url = URL(URL(), priv->uri); + GST_DEBUG_OBJECT(src, "Fetching %s", priv->originalURI.data()); + URL url = URL(URL(), priv->originalURI.data()); ResourceRequest request(url); request.setAllowCookies(true); request.setFirstPartyForCookies(url); + priv->size = 0; + if (priv->player) request.setHTTPReferrer(priv->player->referrer()); + if (priv->httpMethod.get()) + request.setHTTPMethod(priv->httpMethod.get()); + #if USE(SOUP) - // Let's disable HTTP Accept-Encoding here as we don't want the received response to be - // encoded in any way as we need to rely on the proper size of the returned data on + // By default, HTTP Accept-Encoding is disabled here as we don't + // want the received response to be encoded in any way as we need + // to rely on the proper size of the returned data on // didReceiveResponse. // If Accept-Encoding is used, the server may send the data in encoded format and // request.expectedContentLength() will have the "wrong" size (the size of the // compressed data), even though the data received in didReceiveData is uncompressed. - request.setAcceptEncoding(false); + // This is however useful to enable for adaptive streaming + // scenarios, when the demuxer needs to download playlists. + if (!priv->compress) + request.setAcceptEncoding(false); #endif // Let Apple web servers know we want to access their nice movie trailers. @@ -516,36 +568,55 @@ static gboolean webKitWebSrcStart(WebKitWebSrc* src) if (priv->requestedOffset) { GUniquePtr<gchar> val(g_strdup_printf("bytes=%" G_GUINT64_FORMAT "-", priv->requestedOffset)); - request.setHTTPHeaderField("Range", val.get()); + request.setHTTPHeaderField(HTTPHeaderName::Range, val.get()); } priv->offset = priv->requestedOffset; - if (priv->iradioMode) - request.setHTTPHeaderField("icy-metadata", "1"); + if (!priv->keepAlive) { + GST_DEBUG_OBJECT(src, "Persistent connection support disabled"); + request.setHTTPHeaderField(HTTPHeaderName::Connection, "close"); + } - // Needed to use DLNA streaming servers - request.setHTTPHeaderField("transferMode.dlna", "Streaming"); + if (priv->extraHeaders) + gst_structure_foreach(priv->extraHeaders.get(), webKitWebSrcProcessExtraHeaders, &request); - if (priv->player) { - if (CachedResourceLoader* loader = priv->player->cachedResourceLoader()) - priv->client = new CachedResourceStreamingClient(src, loader, request, priv->player->mediaPlayerClient()->mediaPlayerCORSMode()); - } + // We always request Icecast/Shoutcast metadata, just in case ... + request.setHTTPHeaderField(HTTPHeaderName::IcyMetadata, "1"); - if (!priv->client) - priv->client = new ResourceHandleStreamingClient(src, request); + if (!priv->player || !priv->createdInMainThread) { + priv->client = ResourceHandleStreamingClient::create(src, WTFMove(request)); + if (priv->client->loadFailed()) { + GST_ERROR_OBJECT(src, "Failed to setup streaming client"); + locker.unlock(); + webKitWebSrcStop(src); + } else + GST_DEBUG_OBJECT(src, "Started request"); + return; + } - if (!priv->client || priv->client->loadFailed()) { - GST_ERROR_OBJECT(src, "Failed to setup streaming client"); - if (priv->client) { - delete priv->client; - priv->client = 0; + locker.unlock(); + GRefPtr<WebKitWebSrc> protector = WTF::ensureGRef(src); + priv->notifier->notify(MainThreadSourceNotification::Start, [protector, request = WTFMove(request)] { + WebKitWebSrcPrivate* priv = protector->priv; + + WTF::GMutexLocker<GMutex> locker(*GST_OBJECT_GET_LOCK(protector.get())); + if (!priv->loader) + priv->loader = priv->player->createResourceLoader(); + + PlatformMediaResourceLoader::LoadOptions loadOptions = 0; + if (request.url().protocolIsBlob()) + loadOptions |= PlatformMediaResourceLoader::LoadOption::BufferData; + priv->resource = priv->loader->requestResource(ResourceRequest(request), loadOptions); + if (priv->resource) { + priv->resource->setClient(std::make_unique<CachedResourceStreamingClient>(protector.get(), ResourceRequest(request))); + GST_DEBUG_OBJECT(protector.get(), "Started request"); + } else { + GST_ERROR_OBJECT(protector.get(), "Failed to setup streaming client"); + priv->loader = nullptr; + locker.unlock(); + webKitWebSrcStop(protector.get()); } - locker.unlock(); - webKitWebSrcStop(src); - return FALSE; - } - GST_DEBUG_OBJECT(src, "Started request"); - return FALSE; + }); } static GstStateChangeReturn webKitWebSrcChangeState(GstElement* element, GstStateChange transition) @@ -559,7 +630,7 @@ static GstStateChangeReturn webKitWebSrcChangeState(GstElement* element, GstStat if (!priv->appsrc) { gst_element_post_message(element, gst_missing_element_message_new(element, "appsrc")); - GST_ELEMENT_ERROR(src, CORE, MISSING_PLUGIN, (0), ("no appsrc")); + GST_ELEMENT_ERROR(src, CORE, MISSING_PLUGIN, (nullptr), ("no appsrc")); return GST_STATE_CHANGE_FAILURE; } break; @@ -573,18 +644,19 @@ static GstStateChangeReturn webKitWebSrcChangeState(GstElement* element, GstStat return ret; } - WTF::GMutexLocker locker(GST_OBJECT_GET_LOCK(src)); switch (transition) { case GST_STATE_CHANGE_READY_TO_PAUSED: + { GST_DEBUG_OBJECT(src, "READY->PAUSED"); - priv->startID = g_idle_add_full(G_PRIORITY_DEFAULT, (GSourceFunc) webKitWebSrcStart, gst_object_ref(src), (GDestroyNotify) gst_object_unref); + webKitWebSrcStart(src); break; + } case GST_STATE_CHANGE_PAUSED_TO_READY: + { GST_DEBUG_OBJECT(src, "PAUSED->READY"); - // cancel pending sources - removeTimeoutSources(src); - priv->stopID = g_idle_add_full(G_PRIORITY_DEFAULT, (GSourceFunc) webKitWebSrcStop, gst_object_ref(src), (GDestroyNotify) gst_object_unref); + webKitWebSrcStop(src); break; + } default: break; } @@ -601,10 +673,10 @@ static gboolean webKitWebSrcQueryWithParent(GstPad* pad, GstObject* parent, GstQ case GST_QUERY_DURATION: { GstFormat format; - gst_query_parse_duration(query, &format, NULL); + gst_query_parse_duration(query, &format, nullptr); GST_DEBUG_OBJECT(src, "duration query in format %s", gst_format_get_name(format)); - WTF::GMutexLocker locker(GST_OBJECT_GET_LOCK(src)); + WTF::GMutexLocker<GMutex> locker(*GST_OBJECT_GET_LOCK(src)); if (format == GST_FORMAT_BYTES && src->priv->size > 0) { gst_query_set_duration(query, format, src->priv->size); result = TRUE; @@ -612,8 +684,19 @@ static gboolean webKitWebSrcQueryWithParent(GstPad* pad, GstObject* parent, GstQ break; } case GST_QUERY_URI: { - WTF::GMutexLocker locker(GST_OBJECT_GET_LOCK(src)); - gst_query_set_uri(query, src->priv->uri); + WTF::GMutexLocker<GMutex> locker(*GST_OBJECT_GET_LOCK(src)); + gst_query_set_uri(query, src->priv->originalURI.data()); + if (!src->priv->redirectedURI.isNull()) + gst_query_set_uri_redirection(query, src->priv->redirectedURI.data()); + result = TRUE; + break; + } + case GST_QUERY_SCHEDULING: { + GstSchedulingFlags flags; + int minSize, maxSize, align; + + gst_query_parse_scheduling(query, &flags, &minSize, &maxSize, &align); + gst_query_set_scheduling(query, static_cast<GstSchedulingFlags>(flags | GST_SCHEDULING_FLAG_BANDWIDTH_LIMITED), minSize, maxSize, align); result = TRUE; break; } @@ -632,7 +715,7 @@ static gboolean webKitWebSrcQueryWithParent(GstPad* pad, GstObject* parent, GstQ static bool urlHasSupportedProtocol(const URL& url) { - return url.isValid() && (url.protocolIsInHTTPFamily() || url.protocolIs("blob")); + return url.isValid() && (url.protocolIsInHTTPFamily() || url.protocolIsBlob()); } // uri handler interface @@ -644,7 +727,7 @@ static GstURIType webKitWebSrcUriGetType(GType) const gchar* const* webKitWebSrcGetProtocols(GType) { - static const char* protocols[] = {"http", "https", "blob", 0 }; + static const char* protocols[] = {"http", "https", "blob", nullptr }; return protocols; } @@ -653,8 +736,8 @@ static gchar* webKitWebSrcGetUri(GstURIHandler* handler) WebKitWebSrc* src = WEBKIT_WEB_SRC(handler); gchar* ret; - WTF::GMutexLocker locker(GST_OBJECT_GET_LOCK(src)); - ret = g_strdup(src->priv->uri); + WTF::GMutexLocker<GMutex> locker(*GST_OBJECT_GET_LOCK(src)); + ret = g_strdup(src->priv->originalURI.data()); return ret; } @@ -668,11 +751,10 @@ static gboolean webKitWebSrcSetUri(GstURIHandler* handler, const gchar* uri, GEr return FALSE; } - WTF::GMutexLocker locker(GST_OBJECT_GET_LOCK(src)); - - g_free(priv->uri); - priv->uri = 0; + WTF::GMutexLocker<GMutex> locker(*GST_OBJECT_GET_LOCK(src)); + priv->redirectedURI = CString(); + priv->originalURI = CString(); if (!uri) return TRUE; @@ -682,7 +764,7 @@ static gboolean webKitWebSrcSetUri(GstURIHandler* handler, const gchar* uri, GEr return FALSE; } - priv->uri = g_strdup(url.string().utf8().data()); + priv->originalURI = url.string().utf8(); return TRUE; } @@ -696,152 +778,122 @@ static void webKitWebSrcUriHandlerInit(gpointer gIface, gpointer) iface->set_uri = webKitWebSrcSetUri; } -// appsrc callbacks - -static gboolean webKitWebSrcNeedDataMainCb(WebKitWebSrc* src) -{ - WebKitWebSrcPrivate* priv = src->priv; - - ASSERT(isMainThread()); - - WTF::GMutexLocker locker(GST_OBJECT_GET_LOCK(src)); - // already stopped - if (!priv->needDataID) - return FALSE; - - priv->paused = FALSE; - priv->needDataID = 0; - locker.unlock(); - - if (priv->client) - priv->client->setDefersLoading(false); - return FALSE; -} - -static void webKitWebSrcNeedDataCb(GstAppSrc*, guint length, gpointer userData) +static void webKitWebSrcNeedData(WebKitWebSrc* src) { - WebKitWebSrc* src = WEBKIT_WEB_SRC(userData); WebKitWebSrcPrivate* priv = src->priv; - GST_DEBUG_OBJECT(src, "Need more data: %u", length); + GST_DEBUG_OBJECT(src, "Need more data"); - WTF::GMutexLocker locker(GST_OBJECT_GET_LOCK(src)); - if (priv->needDataID || !priv->paused) { - return; + { + WTF::GMutexLocker<GMutex> locker(*GST_OBJECT_GET_LOCK(src)); + if (!priv->paused) + return; + priv->paused = false; + if (priv->client) { + priv->client->setDefersLoading(false); + return; + } } - priv->needDataID = g_idle_add_full(G_PRIORITY_DEFAULT, (GSourceFunc) webKitWebSrcNeedDataMainCb, gst_object_ref(src), (GDestroyNotify) gst_object_unref); + GRefPtr<WebKitWebSrc> protector = WTF::ensureGRef(src); + priv->notifier->notify(MainThreadSourceNotification::NeedData, [protector] { + WebKitWebSrcPrivate* priv = protector->priv; + if (priv->resource) + priv->resource->setDefersLoading(false); + }); } -static gboolean webKitWebSrcEnoughDataMainCb(WebKitWebSrc* src) +static void webKitWebSrcEnoughData(WebKitWebSrc* src) { WebKitWebSrcPrivate* priv = src->priv; - ASSERT(isMainThread()); - - WTF::GMutexLocker locker(GST_OBJECT_GET_LOCK(src)); - // already stopped - if (!priv->enoughDataID) - return FALSE; - - priv->paused = TRUE; - priv->enoughDataID = 0; - locker.unlock(); - - if (priv->client) - priv->client->setDefersLoading(true); - return FALSE; -} - -static void webKitWebSrcEnoughDataCb(GstAppSrc*, gpointer userData) -{ - WebKitWebSrc* src = WEBKIT_WEB_SRC(userData); - WebKitWebSrcPrivate* priv = src->priv; - GST_DEBUG_OBJECT(src, "Have enough data"); - WTF::GMutexLocker locker(GST_OBJECT_GET_LOCK(src)); - if (priv->enoughDataID || priv->paused) { - return; + { + WTF::GMutexLocker<GMutex> locker(*GST_OBJECT_GET_LOCK(src)); + if (priv->paused) + return; + priv->paused = true; + if (priv->client) { + priv->client->setDefersLoading(true); + return; + } } - priv->enoughDataID = g_idle_add_full(G_PRIORITY_DEFAULT, (GSourceFunc) webKitWebSrcEnoughDataMainCb, gst_object_ref(src), (GDestroyNotify) gst_object_unref); + GRefPtr<WebKitWebSrc> protector = WTF::ensureGRef(src); + priv->notifier->notify(MainThreadSourceNotification::EnoughData, [protector] { + WebKitWebSrcPrivate* priv = protector->priv; + if (priv->resource) + priv->resource->setDefersLoading(true); + }); } -static gboolean webKitWebSrcSeekMainCb(WebKitWebSrc* src) +static gboolean webKitWebSrcSeek(WebKitWebSrc* src, guint64 offset) { WebKitWebSrcPrivate* priv = src->priv; - ASSERT(isMainThread()); + { + WTF::GMutexLocker<GMutex> locker(*GST_OBJECT_GET_LOCK(src)); + if (offset == priv->offset && priv->requestedOffset == priv->offset) + return TRUE; - WTF::GMutexLocker locker(GST_OBJECT_GET_LOCK(src)); - // already stopped - if (!priv->seekID) - return FALSE; - locker.unlock(); + if (!priv->seekable) + return FALSE; - webKitWebSrcStop(src); - webKitWebSrcStart(src); - - return FALSE; -} - -static gboolean webKitWebSrcSeekDataCb(GstAppSrc*, guint64 offset, gpointer userData) -{ - WebKitWebSrc* src = WEBKIT_WEB_SRC(userData); - WebKitWebSrcPrivate* priv = src->priv; + priv->isSeeking = true; + priv->requestedOffset = offset; + } - GST_DEBUG_OBJECT(src, "Seeking to offset: %" G_GUINT64_FORMAT, offset); - WTF::GMutexLocker locker(GST_OBJECT_GET_LOCK(src)); - if (offset == priv->offset && priv->requestedOffset == priv->offset) + GST_DEBUG_OBJECT(src, "Seeking to offset: %" G_GUINT64_FORMAT, src->priv->requestedOffset); + if (priv->client) { + webKitWebSrcStop(src); + webKitWebSrcStart(src); return TRUE; + } - if (!priv->seekable) - return FALSE; - - GST_DEBUG_OBJECT(src, "Doing range-request seek"); - priv->requestedOffset = offset; - - if (priv->seekID) - g_source_remove(priv->seekID); - priv->seekID = g_idle_add_full(G_PRIORITY_DEFAULT, (GSourceFunc) webKitWebSrcSeekMainCb, gst_object_ref(src), (GDestroyNotify) gst_object_unref); + GRefPtr<WebKitWebSrc> protector = WTF::ensureGRef(src); + priv->notifier->notify(MainThreadSourceNotification::Seek, [protector] { + webKitWebSrcStop(protector.get()); + webKitWebSrcStart(protector.get()); + }); return TRUE; } void webKitWebSrcSetMediaPlayer(WebKitWebSrc* src, WebCore::MediaPlayer* player) { ASSERT(player); - WTF::GMutexLocker locker(GST_OBJECT_GET_LOCK(src)); + ASSERT(src->priv->createdInMainThread); + WTF::GMutexLocker<GMutex> locker(*GST_OBJECT_GET_LOCK(src)); src->priv->player = player; } bool webKitSrcPassedCORSAccessCheck(WebKitWebSrc* src) { - return src->priv->corsAccessCheck == CORSSuccess; + return src->priv->didPassAccessControlCheck; } -StreamingClient::StreamingClient(WebKitWebSrc* src) - : m_src(static_cast<GstElement*>(gst_object_ref(src))) +StreamingClient::StreamingClient(WebKitWebSrc* src, ResourceRequest&& request) + : m_src(GST_ELEMENT(src)) + , m_request(WTFMove(request)) { } StreamingClient::~StreamingClient() { - gst_object_unref(m_src); } char* StreamingClient::createReadBuffer(size_t requestedSize, size_t& actualSize) { - WebKitWebSrc* src = WEBKIT_WEB_SRC(m_src); + WebKitWebSrc* src = WEBKIT_WEB_SRC(m_src.get()); WebKitWebSrcPrivate* priv = src->priv; ASSERT(!priv->buffer); GstBuffer* buffer = gst_buffer_new_and_alloc(requestedSize); - mapGstBuffer(buffer); + mapGstBuffer(buffer, GST_MAP_WRITE); - WTF::GMutexLocker locker(GST_OBJECT_GET_LOCK(src)); + WTF::GMutexLocker<GMutex> locker(*GST_OBJECT_GET_LOCK(src)); priv->buffer = adoptGRef(buffer); locker.unlock(); @@ -849,29 +901,27 @@ char* StreamingClient::createReadBuffer(size_t requestedSize, size_t& actualSize return getGstBufferDataPointer(buffer); } -void StreamingClient::handleResponseReceived(const ResourceResponse& response, CORSAccessCheckResult corsAccessCheck) +void StreamingClient::handleResponseReceived(const ResourceResponse& response) { - WebKitWebSrc* src = WEBKIT_WEB_SRC(m_src); + WebKitWebSrc* src = WEBKIT_WEB_SRC(m_src.get()); WebKitWebSrcPrivate* priv = src->priv; GST_DEBUG_OBJECT(src, "Received response: %d", response.httpStatusCode()); - if (response.httpStatusCode() >= 400 || corsAccessCheck == CORSFailure) { - // Received error code or CORS check failed - if (corsAccessCheck == CORSFailure) - GST_ELEMENT_ERROR(src, RESOURCE, READ, ("Cross-origin stream load denied by Cross-Origin Resource Sharing policy."), (nullptr)); - else - GST_ELEMENT_ERROR(src, RESOURCE, READ, ("Received %d HTTP error code", response.httpStatusCode()), (nullptr)); + auto responseURI = response.url().string().utf8(); + if (priv->originalURI != responseURI) + priv->redirectedURI = WTFMove(responseURI); + + if (response.httpStatusCode() >= 400) { + GST_ELEMENT_ERROR(src, RESOURCE, READ, ("Received %d HTTP error code", response.httpStatusCode()), (nullptr)); gst_app_src_end_of_stream(priv->appsrc); webKitWebSrcStop(src); return; } - WTF::GMutexLocker locker(GST_OBJECT_GET_LOCK(src)); + WTF::GMutexLocker<GMutex> locker(*GST_OBJECT_GET_LOCK(src)); - priv->corsAccessCheck = corsAccessCheck; - - if (priv->seekID) { + if (priv->isSeeking) { GST_DEBUG_OBJECT(src, "Seek in progress, ignoring response"); return; } @@ -896,43 +946,9 @@ void StreamingClient::handleResponseReceived(const ResourceResponse& response, C length += priv->requestedOffset; priv->size = length >= 0 ? length : 0; - priv->seekable = length > 0 && g_ascii_strcasecmp("none", response.httpHeaderField("Accept-Ranges").utf8().data()); - - // Wait until we unlock to send notifications - g_object_freeze_notify(G_OBJECT(src)); - - GstTagList* tags = gst_tag_list_new_empty(); - String value = response.httpHeaderField("icy-name"); - if (!value.isEmpty()) { - g_free(priv->iradioName); - priv->iradioName = g_strdup(value.utf8().data()); - g_object_notify(G_OBJECT(src), "iradio-name"); - gst_tag_list_add(tags, GST_TAG_MERGE_REPLACE, GST_TAG_ORGANIZATION, priv->iradioName, NULL); - } - value = response.httpHeaderField("icy-genre"); - if (!value.isEmpty()) { - g_free(priv->iradioGenre); - priv->iradioGenre = g_strdup(value.utf8().data()); - g_object_notify(G_OBJECT(src), "iradio-genre"); - gst_tag_list_add(tags, GST_TAG_MERGE_REPLACE, GST_TAG_GENRE, priv->iradioGenre, NULL); - } - value = response.httpHeaderField("icy-url"); - if (!value.isEmpty()) { - g_free(priv->iradioUrl); - priv->iradioUrl = g_strdup(value.utf8().data()); - g_object_notify(G_OBJECT(src), "iradio-url"); - gst_tag_list_add(tags, GST_TAG_MERGE_REPLACE, GST_TAG_LOCATION, priv->iradioUrl, NULL); - } - value = response.httpHeaderField("icy-title"); - if (!value.isEmpty()) { - g_free(priv->iradioTitle); - priv->iradioTitle = g_strdup(value.utf8().data()); - g_object_notify(G_OBJECT(src), "iradio-title"); - gst_tag_list_add(tags, GST_TAG_MERGE_REPLACE, GST_TAG_TITLE, priv->iradioTitle, NULL); - } + priv->seekable = length > 0 && g_ascii_strcasecmp("none", response.httpHeaderField(HTTPHeaderName::AcceptRanges).utf8().data()); locker.unlock(); - g_object_thaw_notify(G_OBJECT(src)); // notify size/duration if (length > 0) { @@ -940,33 +956,30 @@ void StreamingClient::handleResponseReceived(const ResourceResponse& response, C } else gst_app_src_set_size(priv->appsrc, -1); - // icecast stuff - value = response.httpHeaderField("icy-metaint"); - if (!value.isEmpty()) { - gchar* endptr = 0; - gint64 icyMetaInt = g_ascii_strtoll(value.utf8().data(), &endptr, 10); - - if (endptr && *endptr == '\0' && icyMetaInt > 0) { - GRefPtr<GstCaps> caps = adoptGRef(gst_caps_new_simple("application/x-icy", "metadata-interval", G_TYPE_INT, (gint) icyMetaInt, NULL)); - - gst_app_src_set_caps(priv->appsrc, caps.get()); - } - } else - gst_app_src_set_caps(priv->appsrc, 0); - - // notify tags - if (gst_tag_list_is_empty(tags)) - gst_tag_list_unref(tags); - else - gst_pad_push_event(priv->srcpad, gst_event_new_tag(tags)); + gst_app_src_set_caps(priv->appsrc, nullptr); + + // Emit a GST_EVENT_CUSTOM_DOWNSTREAM_STICKY event to let GStreamer know about the HTTP headers sent and received. + GstStructure* httpHeaders = gst_structure_new_empty("http-headers"); + gst_structure_set(httpHeaders, "uri", G_TYPE_STRING, priv->originalURI.data(), nullptr); + if (!priv->redirectedURI.isNull()) + gst_structure_set(httpHeaders, "redirection-uri", G_TYPE_STRING, priv->redirectedURI.data(), nullptr); + GUniquePtr<GstStructure> headers(gst_structure_new_empty("request-headers")); + for (const auto& header : m_request.httpHeaderFields()) + gst_structure_set(headers.get(), header.key.utf8().data(), G_TYPE_STRING, header.value.utf8().data(), nullptr); + gst_structure_set(httpHeaders, "request-headers", GST_TYPE_STRUCTURE, headers.get(), nullptr); + headers.reset(gst_structure_new_empty("response-headers")); + for (const auto& header : response.httpHeaderFields()) + gst_structure_set(headers.get(), header.key.utf8().data(), G_TYPE_STRING, header.value.utf8().data(), nullptr); + gst_structure_set(httpHeaders, "response-headers", GST_TYPE_STRUCTURE, headers.get(), nullptr); + gst_pad_push_event(GST_BASE_SRC_PAD(priv->appsrc), gst_event_new_custom(GST_EVENT_CUSTOM_DOWNSTREAM_STICKY, httpHeaders)); } void StreamingClient::handleDataReceived(const char* data, int length) { - WebKitWebSrc* src = WEBKIT_WEB_SRC(m_src); + WebKitWebSrc* src = WEBKIT_WEB_SRC(m_src.get()); WebKitWebSrcPrivate* priv = src->priv; - WTF::GMutexLocker locker(GST_OBJECT_GET_LOCK(src)); + WTF::GMutexLocker<GMutex> locker(*GST_OBJECT_GET_LOCK(src)); GST_LOG_OBJECT(src, "Have %lld bytes of data", priv->buffer ? static_cast<long long>(gst_buffer_get_size(priv->buffer.get())) : length); @@ -975,7 +988,7 @@ void StreamingClient::handleDataReceived(const char* data, int length) if (priv->buffer) unmapGstBuffer(priv->buffer.get()); - if (priv->seekID) { + if (priv->isSeeking) { GST_DEBUG_OBJECT(src, "Seek in progress, ignoring data"); priv->buffer.clear(); return; @@ -1025,111 +1038,135 @@ void StreamingClient::handleDataReceived(const char* data, int length) GstFlowReturn ret = gst_app_src_push_buffer(priv->appsrc, priv->buffer.leakRef()); if (ret != GST_FLOW_OK && ret != GST_FLOW_EOS) - GST_ELEMENT_ERROR(src, CORE, FAILED, (0), (0)); + GST_ELEMENT_ERROR(src, CORE, FAILED, (nullptr), (nullptr)); } void StreamingClient::handleNotifyFinished() { - WebKitWebSrc* src = WEBKIT_WEB_SRC(m_src); + WebKitWebSrc* src = WEBKIT_WEB_SRC(m_src.get()); WebKitWebSrcPrivate* priv = src->priv; GST_DEBUG_OBJECT(src, "Have EOS"); - WTF::GMutexLocker locker(GST_OBJECT_GET_LOCK(src)); - if (!priv->seekID) { + WTF::GMutexLocker<GMutex> locker(*GST_OBJECT_GET_LOCK(src)); + if (!priv->isSeeking) { locker.unlock(); gst_app_src_end_of_stream(priv->appsrc); } } -CachedResourceStreamingClient::CachedResourceStreamingClient(WebKitWebSrc* src, CachedResourceLoader* resourceLoader, const ResourceRequest& request, MediaPlayerClient::CORSMode corsMode) - : StreamingClient(src) +CachedResourceStreamingClient::CachedResourceStreamingClient(WebKitWebSrc* src, ResourceRequest&& request) + : StreamingClient(src, WTFMove(request)) { - DataBufferingPolicy bufferingPolicy = request.url().protocolIs("blob") ? BufferData : DoNotBufferData; - RequestOriginPolicy corsPolicy = corsMode != MediaPlayerClient::Unspecified ? PotentiallyCrossOriginEnabled : UseDefaultOriginRestrictionsForType; - StoredCredentials allowCredentials = corsMode == MediaPlayerClient::UseCredentials ? AllowStoredCredentials : DoNotAllowStoredCredentials; - ResourceLoaderOptions options(SendCallbacks, DoNotSniffContent, bufferingPolicy, allowCredentials, DoNotAskClientForCrossOriginCredentials, DoSecurityCheck, corsPolicy); - - CachedResourceRequest cacheRequest(request, options); - - if (corsMode != MediaPlayerClient::Unspecified) { - m_origin = resourceLoader->document() ? resourceLoader->document()->securityOrigin() : nullptr; - updateRequestForAccessControl(cacheRequest.mutableResourceRequest(), m_origin.get(), allowCredentials); - } - - // TODO: Decide whether to use preflight mode for cross-origin requests (see http://wkbug.com/131484). - m_resource = resourceLoader->requestRawResource(cacheRequest); - if (m_resource) - m_resource->addClient(this); } CachedResourceStreamingClient::~CachedResourceStreamingClient() { - if (m_resource) { - m_resource->removeClient(this); - m_resource = 0; - } } -bool CachedResourceStreamingClient::loadFailed() const +#if USE(SOUP) +char* CachedResourceStreamingClient::getOrCreateReadBuffer(PlatformMediaResource&, size_t requestedSize, size_t& actualSize) { - return !m_resource; + return createReadBuffer(requestedSize, actualSize); } +#endif -void CachedResourceStreamingClient::setDefersLoading(bool defers) +void CachedResourceStreamingClient::responseReceived(PlatformMediaResource&, const ResourceResponse& response) { - if (m_resource) - m_resource->setDefersLoading(defers); + WebKitWebSrcPrivate* priv = WEBKIT_WEB_SRC(m_src.get())->priv; + priv->didPassAccessControlCheck = priv->resource->didPassAccessControlCheck(); + handleResponseReceived(response); } -char* CachedResourceStreamingClient::getOrCreateReadBuffer(CachedResource*, size_t requestedSize, size_t& actualSize) +void CachedResourceStreamingClient::dataReceived(PlatformMediaResource&, const char* data, int length) { - return createReadBuffer(requestedSize, actualSize); + handleDataReceived(data, length); } -void CachedResourceStreamingClient::responseReceived(CachedResource* resource, const ResourceResponse& response) +void CachedResourceStreamingClient::accessControlCheckFailed(PlatformMediaResource&, const ResourceError& error) { - CORSAccessCheckResult corsAccessCheck = CORSNoCheck; - if (m_origin) - corsAccessCheck = (m_origin->canRequest(response.url()) || resource->passesAccessControlCheck(m_origin.get())) ? CORSSuccess : CORSFailure; - handleResponseReceived(response, corsAccessCheck); + WebKitWebSrc* src = WEBKIT_WEB_SRC(m_src.get()); + GST_ELEMENT_ERROR(src, RESOURCE, READ, ("%s", error.localizedDescription().utf8().data()), (nullptr)); + gst_app_src_end_of_stream(src->priv->appsrc); + webKitWebSrcStop(src); } -void CachedResourceStreamingClient::dataReceived(CachedResource*, const char* data, int length) +void CachedResourceStreamingClient::loadFailed(PlatformMediaResource&, const ResourceError& error) { - handleDataReceived(data, length); + WebKitWebSrc* src = WEBKIT_WEB_SRC(m_src.get()); + + if (!error.isCancellation()) { + GST_ERROR_OBJECT(src, "Have failure: %s", error.localizedDescription().utf8().data()); + GST_ELEMENT_ERROR(src, RESOURCE, FAILED, ("%s", error.localizedDescription().utf8().data()), (nullptr)); + } + + gst_app_src_end_of_stream(src->priv->appsrc); } -void CachedResourceStreamingClient::notifyFinished(CachedResource* resource) +void CachedResourceStreamingClient::loadFinished(PlatformMediaResource&) { - if (resource->loadFailedOrCanceled()) { - WebKitWebSrc* src = WEBKIT_WEB_SRC(m_src); + handleNotifyFinished(); +} - if (!resource->wasCanceled()) { - const ResourceError& error = resource->resourceError(); - GST_ERROR_OBJECT(src, "Have failure: %s", error.localizedDescription().utf8().data()); - GST_ELEMENT_ERROR(src, RESOURCE, FAILED, ("%s", error.localizedDescription().utf8().data()), (0)); +ResourceHandleStreamingClient::ResourceHandleStreamingClient(WebKitWebSrc* src, ResourceRequest&& request) + : StreamingClient(src, WTFMove(request)) +{ + LockHolder locker(m_initializeRunLoopConditionMutex); + m_thread = createThread("ResourceHandleStreamingClient", [this] { + { + LockHolder locker(m_initializeRunLoopConditionMutex); + m_runLoop = &RunLoop::current(); +#if USE(SOUP) + m_session = std::make_unique<SoupNetworkSession>(); + m_resource = ResourceHandle::create(*m_session, m_request, this, true, false); +#else + // FIXME: This create will hit an assert in debug builds. See https://bugs.webkit.org/show_bug.cgi?id=167003. + m_resource = ResourceHandle::create(nullptr, m_request, this, true, false); +#endif + m_initializeRunLoopCondition.notifyOne(); } - gst_app_src_end_of_stream(src->priv->appsrc); - return; - } + if (!m_resource) + return; - handleNotifyFinished(); + m_runLoop->dispatch([this] { m_resource->setDefersLoading(false); }); + m_runLoop->run(); + }); + m_initializeRunLoopCondition.wait(m_initializeRunLoopConditionMutex); } -ResourceHandleStreamingClient::ResourceHandleStreamingClient(WebKitWebSrc* src, const ResourceRequest& request) - : StreamingClient(src) +ResourceHandleStreamingClient::~ResourceHandleStreamingClient() { - m_resource = ResourceHandle::create(0 /*context*/, request, this, false, false); + if (m_thread) { + detachThread(m_thread); + m_thread = 0; + } } -ResourceHandleStreamingClient::~ResourceHandleStreamingClient() +void ResourceHandleStreamingClient::cleanupAndStopRunLoop() +{ + m_resource->clearClient(); + m_resource->cancel(); + m_resource = nullptr; +#if USE(SOUP) + m_session = nullptr; +#endif + m_runLoop->stop(); +} + +void ResourceHandleStreamingClient::invalidate() { - if (m_resource) { - m_resource->cancel(); - m_resource.release(); - m_resource = 0; + if (m_runLoop == &RunLoop::current()) { + cleanupAndStopRunLoop(); + return; } + + LockHolder locker(m_terminateRunLoopConditionMutex); + m_runLoop->dispatch([this, protectedThis = makeRef(*this)] { + cleanupAndStopRunLoop(); + LockHolder locker(m_terminateRunLoopConditionMutex); + m_terminateRunLoopCondition.notifyOne(); + }); + m_terminateRunLoopCondition.wait(m_terminateRunLoopConditionMutex); } bool ResourceHandleStreamingClient::loadFailed() const @@ -1139,31 +1176,40 @@ bool ResourceHandleStreamingClient::loadFailed() const void ResourceHandleStreamingClient::setDefersLoading(bool defers) { - if (m_resource) - m_resource->setDefersLoading(defers); + m_runLoop->dispatch([this, protectedThis = makeRef(*this), defers] { + if (m_resource) + m_resource->setDefersLoading(defers); + }); } +#if USE(SOUP) char* ResourceHandleStreamingClient::getOrCreateReadBuffer(size_t requestedSize, size_t& actualSize) { return createReadBuffer(requestedSize, actualSize); } +#endif -void ResourceHandleStreamingClient::willSendRequest(ResourceHandle*, ResourceRequest&, const ResourceResponse&) +ResourceRequest ResourceHandleStreamingClient::willSendRequest(ResourceHandle*, ResourceRequest&& request, ResourceResponse&&) { + return WTFMove(request); } -void ResourceHandleStreamingClient::didReceiveResponse(ResourceHandle*, const ResourceResponse& response) +void ResourceHandleStreamingClient::didReceiveResponse(ResourceHandle*, ResourceResponse&& response) { - handleResponseReceived(response, CORSNoCheck); + if (m_resource) + handleResponseReceived(response); } -void ResourceHandleStreamingClient::didReceiveData(ResourceHandle*, const char* data, unsigned length, int) +void ResourceHandleStreamingClient::didReceiveData(ResourceHandle*, const char* /* data */, unsigned /* length */, int) { ASSERT_NOT_REACHED(); } -void ResourceHandleStreamingClient::didReceiveBuffer(ResourceHandle*, PassRefPtr<SharedBuffer> buffer, int /* encodedLength */) +void ResourceHandleStreamingClient::didReceiveBuffer(ResourceHandle*, Ref<SharedBuffer>&& buffer, int /* encodedLength */) { + if (!m_resource) + return; + // This pattern is suggested by SharedBuffer.h. const char* segment; unsigned position = 0; @@ -1175,44 +1221,45 @@ void ResourceHandleStreamingClient::didReceiveBuffer(ResourceHandle*, PassRefPtr void ResourceHandleStreamingClient::didFinishLoading(ResourceHandle*, double) { - handleNotifyFinished(); + if (m_resource) + handleNotifyFinished(); } void ResourceHandleStreamingClient::didFail(ResourceHandle*, const ResourceError& error) { - WebKitWebSrc* src = WEBKIT_WEB_SRC(m_src); + WebKitWebSrc* src = WEBKIT_WEB_SRC(m_src.get()); GST_ERROR_OBJECT(src, "Have failure: %s", error.localizedDescription().utf8().data()); - GST_ELEMENT_ERROR(src, RESOURCE, FAILED, ("%s", error.localizedDescription().utf8().data()), (0)); + GST_ELEMENT_ERROR(src, RESOURCE, FAILED, ("%s", error.localizedDescription().utf8().data()), (nullptr)); gst_app_src_end_of_stream(src->priv->appsrc); } void ResourceHandleStreamingClient::wasBlocked(ResourceHandle*) { - WebKitWebSrc* src = WEBKIT_WEB_SRC(m_src); + WebKitWebSrc* src = WEBKIT_WEB_SRC(m_src.get()); GUniquePtr<gchar> uri; GST_ERROR_OBJECT(src, "Request was blocked"); - WTF::GMutexLocker locker(GST_OBJECT_GET_LOCK(src)); - uri.reset(g_strdup(src->priv->uri)); + WTF::GMutexLocker<GMutex> locker(*GST_OBJECT_GET_LOCK(src)); + uri.reset(g_strdup(src->priv->originalURI.data())); locker.unlock(); - GST_ELEMENT_ERROR(src, RESOURCE, OPEN_READ, ("Access to \"%s\" was blocked", uri.get()), (0)); + GST_ELEMENT_ERROR(src, RESOURCE, OPEN_READ, ("Access to \"%s\" was blocked", uri.get()), (nullptr)); } void ResourceHandleStreamingClient::cannotShowURL(ResourceHandle*) { - WebKitWebSrc* src = WEBKIT_WEB_SRC(m_src); + WebKitWebSrc* src = WEBKIT_WEB_SRC(m_src.get()); GUniquePtr<gchar> uri; GST_ERROR_OBJECT(src, "Cannot show URL"); - WTF::GMutexLocker locker(GST_OBJECT_GET_LOCK(src)); - uri.reset(g_strdup(src->priv->uri)); + WTF::GMutexLocker<GMutex> locker(*GST_OBJECT_GET_LOCK(src)); + uri.reset(g_strdup(src->priv->originalURI.data())); locker.unlock(); - GST_ELEMENT_ERROR(src, RESOURCE, OPEN_READ, ("Can't show \"%s\"", uri.get()), (0)); + GST_ELEMENT_ERROR(src, RESOURCE, OPEN_READ, ("Can't show \"%s\"", uri.get()), (nullptr)); } #endif // USE(GSTREAMER) |