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