1/*
2 *  Copyright (C) 2009, 2010 Sebastian Dröge <sebastian.droege@collabora.co.uk>
3 *  Copyright (C) 2013 Collabora Ltd.
4 *
5 *  This library is free software; you can redistribute it and/or
6 *  modify it under the terms of the GNU Lesser General Public
7 *  License as published by the Free Software Foundation; either
8 *  version 2 of the License, or (at your option) any later version.
9 *
10 *  This library is distributed in the hope that it will be useful,
11 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
12 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13 *  Lesser General Public License for more details.
14 *
15 *  You should have received a copy of the GNU Lesser General Public
16 *  License along with this library; if not, write to the Free Software
17 *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
18 */
19
20#include "config.h"
21#include "WebKitWebSourceGStreamer.h"
22
23#if ENABLE(VIDEO) && USE(GSTREAMER)
24
25#include "Document.h"
26#include "Frame.h"
27#include "FrameLoader.h"
28#include "GRefPtrGStreamer.h"
29#include "GStreamerVersioning.h"
30#include "MediaPlayer.h"
31#include "NetworkingContext.h"
32#include "NotImplemented.h"
33#include "ResourceHandleClient.h"
34#include "ResourceHandleInternal.h"
35#include "ResourceRequest.h"
36#include "ResourceResponse.h"
37#include <gst/app/gstappsrc.h>
38#include <gst/gst.h>
39#include <gst/pbutils/missing-plugins.h>
40#include <wtf/Noncopyable.h>
41#include <wtf/gobject/GOwnPtr.h>
42#include <wtf/gobject/GRefPtr.h>
43#include <wtf/text/CString.h>
44
45/* Premisses:
46 * - webkitsrc may be created from any thread inside gstreamer
47 * - client holds reference to src, so that src is never deleted while client exists
48 * - if the src exists, appsrc also exists
49 * - client is created on start
50 * - client is deleted on stop after cancelling resource handle
51 * - client callbacks are always invoked from main thread
52 * - resource handle methods must always be called from main thread
53 */
54
55using namespace WebCore;
56
57class StreamingClient : public ResourceHandleClient {
58    WTF_MAKE_NONCOPYABLE(StreamingClient); WTF_MAKE_FAST_ALLOCATED;
59    public:
60        StreamingClient(WebKitWebSrc*);
61        virtual ~StreamingClient();
62
63        virtual void willSendRequest(ResourceHandle*, ResourceRequest&, const ResourceResponse&);
64        virtual void didReceiveResponse(ResourceHandle*, const ResourceResponse&);
65
66        virtual char* getOrCreateReadBuffer(size_t requestedSize, size_t& actualSize);
67
68        virtual void didReceiveData(ResourceHandle*, const char*, int, int);
69        virtual void didFinishLoading(ResourceHandle*, double /*finishTime*/);
70        virtual void didFail(ResourceHandle*, const ResourceError&);
71        virtual void wasBlocked(ResourceHandle*);
72        virtual void cannotShowURL(ResourceHandle*);
73
74    private:
75        WebKitWebSrc* m_src;
76};
77
78#define WEBKIT_WEB_SRC_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE((obj), WEBKIT_TYPE_WEB_SRC, WebKitWebSrcPrivate))
79struct _WebKitWebSrcPrivate {
80    GstAppSrc* appsrc;
81    GstPad* srcpad;
82    gchar* uri;
83
84    RefPtr<WebCore::Frame> frame;
85    WebCore::MediaPlayer* player;
86
87    StreamingClient* client;
88    RefPtr<ResourceHandle> resourceHandle;
89
90    guint64 offset;
91    guint64 size;
92    gboolean seekable;
93    gboolean paused;
94
95    guint64 requestedOffset;
96
97    guint startID;
98    guint stopID;
99    guint needDataID;
100    guint enoughDataID;
101    guint seekID;
102
103    GRefPtr<GstBuffer> buffer;
104
105    // icecast stuff
106    gboolean iradioMode;
107    gchar* iradioName;
108    gchar* iradioGenre;
109    gchar* iradioUrl;
110    gchar* iradioTitle;
111
112    // TRUE if appsrc's version is >= 0.10.27, see
113    // https://bugzilla.gnome.org/show_bug.cgi?id=609423
114    gboolean haveAppSrc27;
115};
116
117enum {
118    PROP_IRADIO_MODE = 1,
119    PROP_IRADIO_NAME,
120    PROP_IRADIO_GENRE,
121    PROP_IRADIO_URL,
122    PROP_IRADIO_TITLE,
123    PROP_LOCATION
124};
125
126static GstStaticPadTemplate srcTemplate = GST_STATIC_PAD_TEMPLATE("src",
127                                                                  GST_PAD_SRC,
128                                                                  GST_PAD_ALWAYS,
129                                                                  GST_STATIC_CAPS_ANY);
130
131GST_DEBUG_CATEGORY_STATIC(webkit_web_src_debug);
132#define GST_CAT_DEFAULT webkit_web_src_debug
133
134static void webKitWebSrcUriHandlerInit(gpointer gIface, gpointer ifaceData);
135
136static void webKitWebSrcDispose(GObject*);
137static void webKitWebSrcFinalize(GObject*);
138static void webKitWebSrcSetProperty(GObject*, guint propertyID, const GValue*, GParamSpec*);
139static void webKitWebSrcGetProperty(GObject*, guint propertyID, GValue*, GParamSpec*);
140static GstStateChangeReturn webKitWebSrcChangeState(GstElement*, GstStateChange);
141
142static gboolean webKitWebSrcQueryWithParent(GstPad*, GstObject*, GstQuery*);
143#ifndef GST_API_VERSION_1
144static gboolean webKitWebSrcQuery(GstPad*, GstQuery*);
145#endif
146
147static void webKitWebSrcNeedDataCb(GstAppSrc*, guint length, gpointer userData);
148static void webKitWebSrcEnoughDataCb(GstAppSrc*, gpointer userData);
149static gboolean webKitWebSrcSeekDataCb(GstAppSrc*, guint64 offset, gpointer userData);
150
151static GstAppSrcCallbacks appsrcCallbacks = {
152    webKitWebSrcNeedDataCb,
153    webKitWebSrcEnoughDataCb,
154    webKitWebSrcSeekDataCb,
155    { 0 }
156};
157
158#define webkit_web_src_parent_class parent_class
159// We split this out into another macro to avoid a check-webkit-style error.
160#define WEBKIT_WEB_SRC_CATEGORY_INIT GST_DEBUG_CATEGORY_INIT(webkit_web_src_debug, "webkitwebsrc", 0, "websrc element");
161G_DEFINE_TYPE_WITH_CODE(WebKitWebSrc, webkit_web_src, GST_TYPE_BIN,
162                         G_IMPLEMENT_INTERFACE(GST_TYPE_URI_HANDLER, webKitWebSrcUriHandlerInit);
163                         WEBKIT_WEB_SRC_CATEGORY_INIT);
164
165static void webkit_web_src_class_init(WebKitWebSrcClass* klass)
166{
167    GObjectClass* oklass = G_OBJECT_CLASS(klass);
168    GstElementClass* eklass = GST_ELEMENT_CLASS(klass);
169
170    oklass->dispose = webKitWebSrcDispose;
171    oklass->finalize = webKitWebSrcFinalize;
172    oklass->set_property = webKitWebSrcSetProperty;
173    oklass->get_property = webKitWebSrcGetProperty;
174
175    gst_element_class_add_pad_template(eklass,
176                                       gst_static_pad_template_get(&srcTemplate));
177    setGstElementClassMetadata(eklass, "WebKit Web source element", "Source", "Handles HTTP/HTTPS uris",
178                               "Sebastian Dröge <sebastian.droege@collabora.co.uk>");
179
180    // icecast stuff
181    g_object_class_install_property(oklass,
182                                    PROP_IRADIO_MODE,
183                                    g_param_spec_boolean("iradio-mode",
184                                                         "iradio-mode",
185                                                         "Enable internet radio mode (extraction of shoutcast/icecast metadata)",
186                                                         FALSE,
187                                                         (GParamFlags) (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)));
188
189    g_object_class_install_property(oklass,
190                                    PROP_IRADIO_NAME,
191                                    g_param_spec_string("iradio-name",
192                                                        "iradio-name",
193                                                        "Name of the stream",
194                                                        0,
195                                                        (GParamFlags) (G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)));
196
197    g_object_class_install_property(oklass,
198                                    PROP_IRADIO_GENRE,
199                                    g_param_spec_string("iradio-genre",
200                                                        "iradio-genre",
201                                                        "Genre of the stream",
202                                                        0,
203                                                        (GParamFlags) (G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)));
204
205    g_object_class_install_property(oklass,
206                                    PROP_IRADIO_URL,
207                                    g_param_spec_string("iradio-url",
208                                                        "iradio-url",
209                                                        "Homepage URL for radio stream",
210                                                        0,
211                                                        (GParamFlags) (G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)));
212
213    g_object_class_install_property(oklass,
214                                    PROP_IRADIO_TITLE,
215                                    g_param_spec_string("iradio-title",
216                                                        "iradio-title",
217                                                        "Name of currently playing song",
218                                                        0,
219                                                        (GParamFlags) (G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)));
220
221
222    /* Allows setting the uri using the 'location' property, which is used
223     * for example by gst_element_make_from_uri() */
224    g_object_class_install_property(oklass,
225                                    PROP_LOCATION,
226                                    g_param_spec_string("location",
227                                                        "location",
228                                                        "Location to read from",
229                                                        0,
230                                                        (GParamFlags) (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)));
231    eklass->change_state = webKitWebSrcChangeState;
232
233    g_type_class_add_private(klass, sizeof(WebKitWebSrcPrivate));
234}
235
236static void webkit_web_src_init(WebKitWebSrc* src)
237{
238    WebKitWebSrcPrivate* priv = WEBKIT_WEB_SRC_GET_PRIVATE(src);
239
240    src->priv = priv;
241
242    priv->appsrc = GST_APP_SRC(gst_element_factory_make("appsrc", 0));
243    if (!priv->appsrc) {
244        GST_ERROR_OBJECT(src, "Failed to create appsrc");
245        return;
246    }
247
248    GstElementFactory* factory = GST_ELEMENT_FACTORY(GST_ELEMENT_GET_CLASS(priv->appsrc)->elementfactory);
249    priv->haveAppSrc27 = gst_plugin_feature_check_version(GST_PLUGIN_FEATURE(factory), 0, 10, 27);
250
251    gst_bin_add(GST_BIN(src), GST_ELEMENT(priv->appsrc));
252
253
254    GRefPtr<GstPad> targetPad = adoptGRef(gst_element_get_static_pad(GST_ELEMENT(priv->appsrc), "src"));
255    priv->srcpad = webkitGstGhostPadFromStaticTemplate(&srcTemplate, "src", targetPad.get());
256
257    gst_element_add_pad(GST_ELEMENT(src), priv->srcpad);
258
259#ifdef GST_API_VERSION_1
260    GST_OBJECT_FLAG_SET(priv->srcpad, GST_PAD_FLAG_NEED_PARENT);
261    gst_pad_set_query_function(priv->srcpad, webKitWebSrcQueryWithParent);
262#else
263    gst_pad_set_query_function(priv->srcpad, webKitWebSrcQuery);
264#endif
265
266    gst_app_src_set_callbacks(priv->appsrc, &appsrcCallbacks, src, 0);
267    gst_app_src_set_emit_signals(priv->appsrc, FALSE);
268    gst_app_src_set_stream_type(priv->appsrc, GST_APP_STREAM_TYPE_SEEKABLE);
269
270    // 512k is a abitrary number but we should choose a value
271    // here to not pause/unpause the SoupMessage too often and
272    // to make sure there's always some data available for
273    // GStreamer to handle.
274    gst_app_src_set_max_bytes(priv->appsrc, 512 * 1024);
275
276    // Emit the need-data signal if the queue contains less
277    // than 20% of data. Without this the need-data signal
278    // is emitted when the queue is empty, we then dispatch
279    // the soup message unpausing to the main loop and from
280    // there unpause the soup message. This already takes
281    // quite some time and libsoup even needs some more time
282    // to actually provide data again. If we do all this
283    // already if the queue is 20% empty, it's much more
284    // likely that libsoup already provides new data before
285    // the queue is really empty.
286    // This might need tweaking for ports not using libsoup.
287    if (priv->haveAppSrc27)
288        g_object_set(priv->appsrc, "min-percent", 20, NULL);
289
290    gst_app_src_set_caps(priv->appsrc, 0);
291    gst_app_src_set_size(priv->appsrc, -1);
292}
293
294static void webKitWebSrcDispose(GObject* object)
295{
296    WebKitWebSrc* src = WEBKIT_WEB_SRC(object);
297    WebKitWebSrcPrivate* priv = src->priv;
298
299    if (priv->buffer) {
300#ifdef GST_API_VERSION_1
301        unmapGstBuffer(priv->buffer.get());
302#endif
303        priv->buffer.clear();
304    }
305
306    GST_CALL_PARENT(G_OBJECT_CLASS, dispose, (object));
307}
308
309static void webKitWebSrcFinalize(GObject* object)
310{
311    WebKitWebSrc* src = WEBKIT_WEB_SRC(object);
312    WebKitWebSrcPrivate* priv = src->priv;
313
314    g_free(priv->uri);
315
316    GST_CALL_PARENT(G_OBJECT_CLASS, finalize, (object));
317}
318
319static void webKitWebSrcSetProperty(GObject* object, guint propID, const GValue* value, GParamSpec* pspec)
320{
321    WebKitWebSrc* src = WEBKIT_WEB_SRC(object);
322    WebKitWebSrcPrivate* priv = src->priv;
323
324    switch (propID) {
325    case PROP_IRADIO_MODE:
326        GST_OBJECT_LOCK(src);
327        priv->iradioMode = g_value_get_boolean(value);
328        GST_OBJECT_UNLOCK(src);
329        break;
330    case PROP_LOCATION:
331#ifdef GST_API_VERSION_1
332        gst_uri_handler_set_uri(reinterpret_cast<GstURIHandler*>(src), g_value_get_string(value), 0);
333#else
334        gst_uri_handler_set_uri(reinterpret_cast<GstURIHandler*>(src), g_value_get_string(value));
335#endif
336        break;
337    default:
338        G_OBJECT_WARN_INVALID_PROPERTY_ID(object, propID, pspec);
339        break;
340    }
341}
342
343static void webKitWebSrcGetProperty(GObject* object, guint propID, GValue* value, GParamSpec* pspec)
344{
345    WebKitWebSrc* src = WEBKIT_WEB_SRC(object);
346    WebKitWebSrcPrivate* priv = src->priv;
347
348    GST_OBJECT_LOCK(src);
349    switch (propID) {
350    case PROP_IRADIO_MODE:
351        g_value_set_boolean(value, priv->iradioMode);
352        break;
353    case PROP_IRADIO_NAME:
354        g_value_set_string(value, priv->iradioName);
355        break;
356    case PROP_IRADIO_GENRE:
357        g_value_set_string(value, priv->iradioGenre);
358        break;
359    case PROP_IRADIO_URL:
360        g_value_set_string(value, priv->iradioUrl);
361        break;
362    case PROP_IRADIO_TITLE:
363        g_value_set_string(value, priv->iradioTitle);
364        break;
365    case PROP_LOCATION:
366        g_value_set_string(value, priv->uri);
367        break;
368    default:
369        G_OBJECT_WARN_INVALID_PROPERTY_ID(object, propID, pspec);
370        break;
371    }
372    GST_OBJECT_UNLOCK(src);
373}
374
375// must be called on main thread and with object unlocked
376static gboolean webKitWebSrcStop(WebKitWebSrc* src)
377{
378    WebKitWebSrcPrivate* priv = src->priv;
379    gboolean seeking;
380
381    GST_OBJECT_LOCK(src);
382
383    seeking = priv->seekID;
384
385    if (priv->startID) {
386        g_source_remove(priv->startID);
387        priv->startID = 0;
388    }
389
390    if (priv->resourceHandle) {
391        priv->resourceHandle->cancel();
392        priv->resourceHandle.release();
393    }
394    priv->resourceHandle = 0;
395
396    if (priv->client) {
397        delete priv->client;
398        priv->client = 0;
399    }
400
401    if (priv->frame && !seeking)
402        priv->frame.clear();
403
404    priv->player = 0;
405
406    if (priv->buffer) {
407#ifdef GST_API_VERSION_1
408        unmapGstBuffer(priv->buffer.get());
409#endif
410        priv->buffer.clear();
411    }
412
413    if (priv->needDataID)
414        g_source_remove(priv->needDataID);
415    priv->needDataID = 0;
416
417    if (priv->enoughDataID)
418        g_source_remove(priv->enoughDataID);
419    priv->enoughDataID = 0;
420
421    if (priv->seekID)
422        g_source_remove(priv->seekID);
423    priv->seekID = 0;
424
425    priv->paused = FALSE;
426
427    g_free(priv->iradioName);
428    priv->iradioName = 0;
429
430    g_free(priv->iradioGenre);
431    priv->iradioGenre = 0;
432
433    g_free(priv->iradioUrl);
434    priv->iradioUrl = 0;
435
436    g_free(priv->iradioTitle);
437    priv->iradioTitle = 0;
438
439    priv->offset = 0;
440    priv->seekable = FALSE;
441
442    if (!seeking) {
443        priv->size = 0;
444        priv->requestedOffset = 0;
445    }
446
447    priv->stopID = 0;
448    GST_OBJECT_UNLOCK(src);
449
450    if (priv->appsrc) {
451        gst_app_src_set_caps(priv->appsrc, 0);
452        if (!seeking)
453            gst_app_src_set_size(priv->appsrc, -1);
454    }
455
456    GST_DEBUG_OBJECT(src, "Stopped request");
457
458    return FALSE;
459}
460
461// must be called on main thread and with object unlocked
462static gboolean webKitWebSrcStart(WebKitWebSrc* src)
463{
464    WebKitWebSrcPrivate* priv = src->priv;
465
466    GST_OBJECT_LOCK(src);
467    if (!priv->uri) {
468        GST_ERROR_OBJECT(src, "No URI provided");
469        GST_OBJECT_UNLOCK(src);
470        webKitWebSrcStop(src);
471        return FALSE;
472    }
473
474    KURL url = KURL(KURL(), priv->uri);
475
476    ResourceRequest request(url);
477    request.setAllowCookies(true);
478
479    NetworkingContext* context = 0;
480    FrameLoader* loader = priv->frame ? priv->frame->loader() : 0;
481    if (loader) {
482        loader->addExtraFieldsToSubresourceRequest(request);
483        context = loader->networkingContext();
484    }
485
486    if (priv->player)
487        request.setHTTPReferrer(priv->player->referrer());
488
489    // Let Apple web servers know we want to access their nice movie trailers.
490    if (!g_ascii_strcasecmp("movies.apple.com", url.host().utf8().data())
491        || !g_ascii_strcasecmp("trailers.apple.com", url.host().utf8().data()))
492        request.setHTTPUserAgent("Quicktime/7.6.6");
493
494    if (priv->requestedOffset) {
495        GOwnPtr<gchar> val;
496
497        val.set(g_strdup_printf("bytes=%" G_GUINT64_FORMAT "-", priv->requestedOffset));
498        request.setHTTPHeaderField("Range", val.get());
499    }
500    priv->offset = priv->requestedOffset;
501
502    if (priv->iradioMode)
503        request.setHTTPHeaderField("icy-metadata", "1");
504
505    // Needed to use DLNA streaming servers
506    request.setHTTPHeaderField("transferMode.dlna", "Streaming");
507
508    priv->client = new StreamingClient(src);
509    priv->resourceHandle = ResourceHandle::create(context, request, priv->client, false, false);
510    if (!priv->resourceHandle) {
511        GST_ERROR_OBJECT(src, "Failed to create ResourceHandle");
512        GST_OBJECT_UNLOCK(src);
513        webKitWebSrcStop(src);
514    } else {
515        GST_OBJECT_UNLOCK(src);
516        GST_DEBUG_OBJECT(src, "Started request");
517    }
518    return FALSE;
519}
520
521static GstStateChangeReturn webKitWebSrcChangeState(GstElement* element, GstStateChange transition)
522{
523    GstStateChangeReturn ret = GST_STATE_CHANGE_SUCCESS;
524    WebKitWebSrc* src = WEBKIT_WEB_SRC(element);
525    WebKitWebSrcPrivate* priv = src->priv;
526
527    switch (transition) {
528    case GST_STATE_CHANGE_NULL_TO_READY:
529        if (!priv->appsrc) {
530            gst_element_post_message(element,
531                                     gst_missing_element_message_new(element, "appsrc"));
532            GST_ELEMENT_ERROR(src, CORE, MISSING_PLUGIN, (0), ("no appsrc"));
533            return GST_STATE_CHANGE_FAILURE;
534        }
535        break;
536    default:
537        break;
538    }
539
540    ret = GST_ELEMENT_CLASS(parent_class)->change_state(element, transition);
541    if (G_UNLIKELY(ret == GST_STATE_CHANGE_FAILURE)) {
542        GST_DEBUG_OBJECT(src, "State change failed");
543        return ret;
544    }
545
546    switch (transition) {
547    case GST_STATE_CHANGE_READY_TO_PAUSED:
548        GST_DEBUG_OBJECT(src, "READY->PAUSED");
549        GST_OBJECT_LOCK(src);
550        priv->startID = g_timeout_add_full(G_PRIORITY_DEFAULT, 0, (GSourceFunc) webKitWebSrcStart, gst_object_ref(src), (GDestroyNotify) gst_object_unref);
551        GST_OBJECT_UNLOCK(src);
552        break;
553    case GST_STATE_CHANGE_PAUSED_TO_READY:
554        GST_DEBUG_OBJECT(src, "PAUSED->READY");
555        GST_OBJECT_LOCK(src);
556        priv->stopID = g_timeout_add_full(G_PRIORITY_DEFAULT, 0, (GSourceFunc) webKitWebSrcStop, gst_object_ref(src), (GDestroyNotify) gst_object_unref);
557        GST_OBJECT_UNLOCK(src);
558        break;
559    default:
560        break;
561    }
562
563    return ret;
564}
565
566static gboolean webKitWebSrcQueryWithParent(GstPad* pad, GstObject* parent, GstQuery* query)
567{
568    WebKitWebSrc* src = WEBKIT_WEB_SRC(GST_ELEMENT(parent));
569    gboolean result = FALSE;
570
571    switch (GST_QUERY_TYPE(query)) {
572    case GST_QUERY_DURATION: {
573        GstFormat format;
574
575        gst_query_parse_duration(query, &format, NULL);
576
577        GST_DEBUG_OBJECT(src, "duration query in format %s", gst_format_get_name(format));
578        GST_OBJECT_LOCK(src);
579        if ((format == GST_FORMAT_BYTES) && (src->priv->size > 0)) {
580            gst_query_set_duration(query, format, src->priv->size);
581            result = TRUE;
582        }
583        GST_OBJECT_UNLOCK(src);
584        break;
585    }
586    case GST_QUERY_URI: {
587        GST_OBJECT_LOCK(src);
588        gst_query_set_uri(query, src->priv->uri);
589        GST_OBJECT_UNLOCK(src);
590        result = TRUE;
591        break;
592    }
593    default: {
594        GRefPtr<GstPad> target = adoptGRef(gst_ghost_pad_get_target(GST_GHOST_PAD_CAST(pad)));
595
596        // Forward the query to the proxy target pad.
597        if (target)
598            result = gst_pad_query(target.get(), query);
599        break;
600    }
601    }
602
603    return result;
604}
605
606#ifndef GST_API_VERSION_1
607static gboolean webKitWebSrcQuery(GstPad* pad, GstQuery* query)
608{
609    GRefPtr<GstElement> src = adoptGRef(gst_pad_get_parent_element(pad));
610    return webKitWebSrcQueryWithParent(pad, GST_OBJECT(src.get()), query);
611}
612#endif
613
614// uri handler interface
615
616#ifdef GST_API_VERSION_1
617static GstURIType webKitWebSrcUriGetType(GType)
618{
619    return GST_URI_SRC;
620}
621
622const gchar* const* webKitWebSrcGetProtocols(GType)
623{
624    static const char* protocols[] = {"http", "https", 0 };
625    return protocols;
626}
627
628static gchar* webKitWebSrcGetUri(GstURIHandler* handler)
629{
630    WebKitWebSrc* src = WEBKIT_WEB_SRC(handler);
631    gchar* ret;
632
633    GST_OBJECT_LOCK(src);
634    ret = g_strdup(src->priv->uri);
635    GST_OBJECT_UNLOCK(src);
636    return ret;
637}
638
639static gboolean webKitWebSrcSetUri(GstURIHandler* handler, const gchar* uri, GError** error)
640{
641    WebKitWebSrc* src = WEBKIT_WEB_SRC(handler);
642    WebKitWebSrcPrivate* priv = src->priv;
643
644    if (GST_STATE(src) >= GST_STATE_PAUSED) {
645        GST_ERROR_OBJECT(src, "URI can only be set in states < PAUSED");
646        return FALSE;
647    }
648
649    GST_OBJECT_LOCK(src);
650    g_free(priv->uri);
651    priv->uri = 0;
652
653    if (!uri) {
654        GST_OBJECT_UNLOCK(src);
655        return TRUE;
656    }
657
658    KURL url(KURL(), uri);
659
660    if (!url.isValid() || !url.protocolIsInHTTPFamily()) {
661        GST_OBJECT_UNLOCK(src);
662        g_set_error(error, GST_URI_ERROR, GST_URI_ERROR_BAD_URI, "Invalid URI '%s'", uri);
663        return FALSE;
664    }
665
666    priv->uri = g_strdup(url.string().utf8().data());
667    GST_OBJECT_UNLOCK(src);
668    return TRUE;
669}
670
671#else
672static GstURIType webKitWebSrcUriGetType(void)
673{
674    return GST_URI_SRC;
675}
676
677static gchar** webKitWebSrcGetProtocols(void)
678{
679    static gchar* protocols[] = {(gchar*) "http", (gchar*) "https", 0 };
680    return protocols;
681}
682
683static const gchar* webKitWebSrcGetUri(GstURIHandler* handler)
684{
685    WebKitWebSrc* src = WEBKIT_WEB_SRC(handler);
686    gchar* ret;
687
688    GST_OBJECT_LOCK(src);
689    ret = g_strdup(src->priv->uri);
690    GST_OBJECT_UNLOCK(src);
691    return ret;
692}
693
694static gboolean webKitWebSrcSetUri(GstURIHandler* handler, const gchar* uri)
695{
696    WebKitWebSrc* src = WEBKIT_WEB_SRC(handler);
697    WebKitWebSrcPrivate* priv = src->priv;
698
699    if (GST_STATE(src) >= GST_STATE_PAUSED) {
700        GST_ERROR_OBJECT(src, "URI can only be set in states < PAUSED");
701        return FALSE;
702    }
703
704    GST_OBJECT_LOCK(src);
705    g_free(priv->uri);
706    priv->uri = 0;
707
708    if (!uri) {
709        GST_OBJECT_UNLOCK(src);
710        return TRUE;
711    }
712
713    KURL url(KURL(), uri);
714
715    if (!url.isValid() || !url.protocolIsInHTTPFamily()) {
716        GST_OBJECT_UNLOCK(src);
717        GST_ERROR_OBJECT(src, "Invalid URI '%s'", uri);
718        return FALSE;
719    }
720
721    priv->uri = g_strdup(url.string().utf8().data());
722    GST_OBJECT_UNLOCK(src);
723    return TRUE;
724}
725#endif
726
727static void webKitWebSrcUriHandlerInit(gpointer gIface, gpointer)
728{
729    GstURIHandlerInterface* iface = (GstURIHandlerInterface *) gIface;
730
731    iface->get_type = webKitWebSrcUriGetType;
732    iface->get_protocols = webKitWebSrcGetProtocols;
733    iface->get_uri = webKitWebSrcGetUri;
734    iface->set_uri = webKitWebSrcSetUri;
735}
736
737// appsrc callbacks
738
739static gboolean webKitWebSrcNeedDataMainCb(WebKitWebSrc* src)
740{
741    WebKitWebSrcPrivate* priv = src->priv;
742
743    GST_OBJECT_LOCK(src);
744    // already stopped
745    if (!priv->needDataID) {
746        GST_OBJECT_UNLOCK(src);
747        return FALSE;
748    }
749
750    priv->paused = FALSE;
751    priv->needDataID = 0;
752    GST_OBJECT_UNLOCK(src);
753
754    if (priv->resourceHandle)
755        priv->resourceHandle->setDefersLoading(false);
756    return FALSE;
757}
758
759static void webKitWebSrcNeedDataCb(GstAppSrc*, guint length, gpointer userData)
760{
761    WebKitWebSrc* src = WEBKIT_WEB_SRC(userData);
762    WebKitWebSrcPrivate* priv = src->priv;
763
764    GST_DEBUG_OBJECT(src, "Need more data: %u", length);
765
766    GST_OBJECT_LOCK(src);
767    if (priv->needDataID || !priv->paused) {
768        GST_OBJECT_UNLOCK(src);
769        return;
770    }
771
772    priv->needDataID = g_timeout_add_full(G_PRIORITY_DEFAULT, 0, (GSourceFunc) webKitWebSrcNeedDataMainCb, gst_object_ref(src), (GDestroyNotify) gst_object_unref);
773    GST_OBJECT_UNLOCK(src);
774}
775
776static gboolean webKitWebSrcEnoughDataMainCb(WebKitWebSrc* src)
777{
778    WebKitWebSrcPrivate* priv = src->priv;
779
780    GST_OBJECT_LOCK(src);
781    // already stopped
782    if (!priv->enoughDataID) {
783        GST_OBJECT_UNLOCK(src);
784        return FALSE;
785    }
786
787    priv->paused = TRUE;
788    priv->enoughDataID = 0;
789    GST_OBJECT_UNLOCK(src);
790
791    if (priv->resourceHandle)
792        priv->resourceHandle->setDefersLoading(true);
793    return FALSE;
794}
795
796static void webKitWebSrcEnoughDataCb(GstAppSrc*, gpointer userData)
797{
798    WebKitWebSrc* src = WEBKIT_WEB_SRC(userData);
799    WebKitWebSrcPrivate* priv = src->priv;
800
801    GST_DEBUG_OBJECT(src, "Have enough data");
802
803    GST_OBJECT_LOCK(src);
804    if (priv->enoughDataID || priv->paused) {
805        GST_OBJECT_UNLOCK(src);
806        return;
807    }
808
809    priv->enoughDataID = g_timeout_add_full(G_PRIORITY_DEFAULT, 0, (GSourceFunc) webKitWebSrcEnoughDataMainCb, gst_object_ref(src), (GDestroyNotify) gst_object_unref);
810    GST_OBJECT_UNLOCK(src);
811}
812
813static gboolean webKitWebSrcSeekMainCb(WebKitWebSrc* src)
814{
815    webKitWebSrcStop(src);
816    webKitWebSrcStart(src);
817
818    return FALSE;
819}
820
821static gboolean webKitWebSrcSeekDataCb(GstAppSrc*, guint64 offset, gpointer userData)
822{
823    WebKitWebSrc* src = WEBKIT_WEB_SRC(userData);
824    WebKitWebSrcPrivate* priv = src->priv;
825
826    GST_DEBUG_OBJECT(src, "Seeking to offset: %" G_GUINT64_FORMAT, offset);
827    GST_OBJECT_LOCK(src);
828    if (offset == priv->offset && priv->requestedOffset == priv->offset) {
829        GST_OBJECT_UNLOCK(src);
830        return TRUE;
831    }
832
833    if (!priv->seekable) {
834        GST_OBJECT_UNLOCK(src);
835        return FALSE;
836    }
837    if (offset > priv->size) {
838        GST_OBJECT_UNLOCK(src);
839        return FALSE;
840    }
841
842    GST_DEBUG_OBJECT(src, "Doing range-request seek");
843    priv->requestedOffset = offset;
844
845    if (priv->seekID)
846        g_source_remove(priv->seekID);
847    priv->seekID = g_timeout_add_full(G_PRIORITY_DEFAULT, 0, (GSourceFunc) webKitWebSrcSeekMainCb, gst_object_ref(src), (GDestroyNotify) gst_object_unref);
848    GST_OBJECT_UNLOCK(src);
849
850    return TRUE;
851}
852
853void webKitWebSrcSetMediaPlayer(WebKitWebSrc* src, WebCore::MediaPlayer* player)
854{
855    WebKitWebSrcPrivate* priv = src->priv;
856    WebCore::Frame* frame = 0;
857
858    WebCore::Document* document = player->mediaPlayerClient()->mediaPlayerOwningDocument();
859    if (document)
860        frame = document->frame();
861
862    priv->frame = frame;
863    priv->player = player;
864}
865
866StreamingClient::StreamingClient(WebKitWebSrc* src)
867    : m_src(static_cast<WebKitWebSrc*>(gst_object_ref(src)))
868{
869
870}
871
872StreamingClient::~StreamingClient()
873{
874    gst_object_unref(m_src);
875}
876
877void StreamingClient::willSendRequest(ResourceHandle*, ResourceRequest&, const ResourceResponse&)
878{
879}
880
881void StreamingClient::didReceiveResponse(ResourceHandle *handle, const ResourceResponse& response)
882{
883    WebKitWebSrcPrivate* priv = m_src->priv;
884
885    GST_DEBUG_OBJECT(m_src, "Received response: %d", response.httpStatusCode());
886
887    GST_OBJECT_LOCK(m_src);
888
889    // If we seeked we need 206 == PARTIAL_CONTENT
890    if (handle != priv->resourceHandle || (priv->requestedOffset && response.httpStatusCode() != 206)) {
891        GST_OBJECT_UNLOCK(m_src);
892        GST_ELEMENT_ERROR(m_src, RESOURCE, READ, (0), (0));
893        gst_app_src_end_of_stream(priv->appsrc);
894        webKitWebSrcStop(m_src);
895        return;
896    }
897
898    long long length = response.expectedContentLength();
899    if (length > 0)
900        length += priv->requestedOffset;
901
902    priv->size = length >= 0 ? length : 0;
903    priv->seekable = length > 0 && g_ascii_strcasecmp("none", response.httpHeaderField("Accept-Ranges").utf8().data());
904
905#ifdef GST_API_VERSION_1
906    GstTagList* tags = gst_tag_list_new_empty();
907#else
908    GstTagList* tags = gst_tag_list_new();
909#endif
910    String value = response.httpHeaderField("icy-name");
911    if (!value.isEmpty()) {
912        g_free(priv->iradioName);
913        priv->iradioName = g_strdup(value.utf8().data());
914        g_object_notify(G_OBJECT(m_src), "iradio-name");
915        gst_tag_list_add(tags, GST_TAG_MERGE_REPLACE, GST_TAG_ORGANIZATION, priv->iradioName, NULL);
916    }
917    value = response.httpHeaderField("icy-genre");
918    if (!value.isEmpty()) {
919        g_free(priv->iradioGenre);
920        priv->iradioGenre = g_strdup(value.utf8().data());
921        g_object_notify(G_OBJECT(m_src), "iradio-genre");
922        gst_tag_list_add(tags, GST_TAG_MERGE_REPLACE, GST_TAG_GENRE, priv->iradioGenre, NULL);
923    }
924    value = response.httpHeaderField("icy-url");
925    if (!value.isEmpty()) {
926        g_free(priv->iradioUrl);
927        priv->iradioUrl = g_strdup(value.utf8().data());
928        g_object_notify(G_OBJECT(m_src), "iradio-url");
929        gst_tag_list_add(tags, GST_TAG_MERGE_REPLACE, GST_TAG_LOCATION, priv->iradioUrl, NULL);
930    }
931    value = response.httpHeaderField("icy-title");
932    if (!value.isEmpty()) {
933        g_free(priv->iradioTitle);
934        priv->iradioTitle = g_strdup(value.utf8().data());
935        g_object_notify(G_OBJECT(m_src), "iradio-title");
936        gst_tag_list_add(tags, GST_TAG_MERGE_REPLACE, GST_TAG_TITLE, priv->iradioTitle, NULL);
937    }
938
939    GST_OBJECT_UNLOCK(m_src);
940
941    // notify size/duration
942    if (length > 0) {
943        gst_app_src_set_size(priv->appsrc, length);
944
945#ifndef GST_API_VERSION_1
946        if (!priv->haveAppSrc27) {
947            gst_segment_set_duration(&GST_BASE_SRC(priv->appsrc)->segment, GST_FORMAT_BYTES, length);
948            gst_element_post_message(GST_ELEMENT(priv->appsrc),
949                gst_message_new_duration(GST_OBJECT(priv->appsrc),
950                    GST_FORMAT_BYTES, length));
951        }
952#endif
953    } else
954        gst_app_src_set_size(priv->appsrc, -1);
955
956    // icecast stuff
957    value = response.httpHeaderField("icy-metaint");
958    if (!value.isEmpty()) {
959        gchar* endptr = 0;
960        gint64 icyMetaInt = g_ascii_strtoll(value.utf8().data(), &endptr, 10);
961
962        if (endptr && *endptr == '\0' && icyMetaInt > 0) {
963            GRefPtr<GstCaps> caps = adoptGRef(gst_caps_new_simple("application/x-icy", "metadata-interval", G_TYPE_INT, (gint) icyMetaInt, NULL));
964
965            gst_app_src_set_caps(priv->appsrc, caps.get());
966        }
967    } else
968        gst_app_src_set_caps(priv->appsrc, 0);
969
970    // notify tags
971    if (gst_tag_list_is_empty(tags))
972#ifdef GST_API_VERSION_1
973        gst_tag_list_unref(tags);
974#else
975        gst_tag_list_free(tags);
976#endif
977    else
978        notifyGstTagsOnPad(GST_ELEMENT(m_src), priv->srcpad, tags);
979}
980
981void StreamingClient::didReceiveData(ResourceHandle* handle, const char* data, int length, int)
982{
983    WebKitWebSrcPrivate* priv = m_src->priv;
984
985    GST_OBJECT_LOCK(m_src);
986
987    GST_LOG_OBJECT(m_src, "Have %d bytes of data", priv->buffer ? getGstBufferSize(priv->buffer.get()) : length);
988
989    ASSERT(!priv->buffer || data == getGstBufferDataPointer(priv->buffer.get()));
990
991#ifdef GST_API_VERSION_1
992    if (priv->buffer)
993        unmapGstBuffer(priv->buffer.get());
994#endif
995
996    if (priv->seekID || handle != priv->resourceHandle) {
997        GST_OBJECT_UNLOCK(m_src);
998        GST_DEBUG_OBJECT(m_src, "Seek in progress, ignoring data");
999        priv->buffer.clear();
1000        return;
1001    }
1002
1003    // Ports using the GStreamer backend but not the soup implementation of ResourceHandle
1004    // won't be using buffers provided by this client, the buffer is created here in that case.
1005    if (!priv->buffer)
1006        priv->buffer = adoptGRef(createGstBufferForData(data, length));
1007    else
1008        setGstBufferSize(priv->buffer.get(), length);
1009
1010    GST_BUFFER_OFFSET(priv->buffer.get()) = priv->offset;
1011    if (priv->requestedOffset == priv->offset)
1012        priv->requestedOffset += length;
1013    priv->offset += length;
1014    GST_BUFFER_OFFSET_END(priv->buffer.get()) = priv->offset;
1015
1016    GST_OBJECT_UNLOCK(m_src);
1017
1018    GstFlowReturn ret = gst_app_src_push_buffer(priv->appsrc, priv->buffer.leakRef());
1019#ifdef GST_API_VERSION_1
1020    if (ret != GST_FLOW_OK && ret != GST_FLOW_EOS)
1021#else
1022    if (ret != GST_FLOW_OK && ret != GST_FLOW_UNEXPECTED)
1023#endif
1024        GST_ELEMENT_ERROR(m_src, CORE, FAILED, (0), (0));
1025}
1026
1027char* StreamingClient::getOrCreateReadBuffer(size_t requestedSize, size_t& actualSize)
1028{
1029    WebKitWebSrcPrivate* priv = m_src->priv;
1030
1031    ASSERT(!priv->buffer);
1032
1033    GstBuffer* buffer = gst_buffer_new_and_alloc(requestedSize);
1034
1035#ifdef GST_API_VERSION_1
1036    mapGstBuffer(buffer);
1037#endif
1038
1039    priv->buffer = adoptGRef(buffer);
1040
1041    actualSize = getGstBufferSize(buffer);
1042    return getGstBufferDataPointer(buffer);
1043}
1044
1045void StreamingClient::didFinishLoading(ResourceHandle*, double)
1046{
1047    WebKitWebSrcPrivate* priv = m_src->priv;
1048
1049    GST_DEBUG_OBJECT(m_src, "Have EOS");
1050
1051    GST_OBJECT_LOCK(m_src);
1052    if (!priv->seekID) {
1053        GST_OBJECT_UNLOCK(m_src);
1054        gst_app_src_end_of_stream(m_src->priv->appsrc);
1055    } else
1056        GST_OBJECT_UNLOCK(m_src);
1057}
1058
1059void StreamingClient::didFail(ResourceHandle*, const ResourceError& error)
1060{
1061    GST_ERROR_OBJECT(m_src, "Have failure: %s", error.localizedDescription().utf8().data());
1062    GST_ELEMENT_ERROR(m_src, RESOURCE, FAILED, ("%s", error.localizedDescription().utf8().data()), (0));
1063    gst_app_src_end_of_stream(m_src->priv->appsrc);
1064}
1065
1066void StreamingClient::wasBlocked(ResourceHandle*)
1067{
1068    GOwnPtr<gchar> uri;
1069
1070    GST_ERROR_OBJECT(m_src, "Request was blocked");
1071
1072    GST_OBJECT_LOCK(m_src);
1073    uri.set(g_strdup(m_src->priv->uri));
1074    GST_OBJECT_UNLOCK(m_src);
1075
1076    GST_ELEMENT_ERROR(m_src, RESOURCE, OPEN_READ, ("Access to \"%s\" was blocked", uri.get()), (0));
1077}
1078
1079void StreamingClient::cannotShowURL(ResourceHandle*)
1080{
1081    GOwnPtr<gchar> uri;
1082
1083    GST_ERROR_OBJECT(m_src, "Cannot show URL");
1084
1085    GST_OBJECT_LOCK(m_src);
1086    uri.set(g_strdup(m_src->priv->uri));
1087    GST_OBJECT_UNLOCK(m_src);
1088
1089    GST_ELEMENT_ERROR(m_src, RESOURCE, OPEN_READ, ("Can't show \"%s\"", uri.get()), (0));
1090}
1091
1092#endif // USE(GSTREAMER)
1093
1094