1 /* 2 * Copyright (C) 2009, 2010 Sebastian Drge <sebastian.droege (at) collabora.co.uk> 3 * 4 * This library is free software; you can redistribute it and/or 5 * modify it under the terms of the GNU Lesser General Public 6 * License as published by the Free Software Foundation; either 7 * version 2 of the License, or (at your option) any later version. 8 * 9 * This library is distributed in the hope that it will be useful, 10 * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 * Lesser General Public License for more details. 13 * 14 * You should have received a copy of the GNU Lesser General Public 15 * License along with this library; if not, write to the Free Software 16 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 17 */ 18 19 #include "config.h" 20 #include "WebKitWebSourceGStreamer.h" 21 #if USE(GSTREAMER) 22 23 #include "Document.h" 24 #include "GOwnPtr.h" 25 #include "GRefPtr.h" 26 #include "GRefPtrGStreamer.h" 27 #include "NetworkingContext.h" 28 #include "Noncopyable.h" 29 #include "NotImplemented.h" 30 #include "ResourceHandleClient.h" 31 #include "ResourceHandleInternal.h" 32 #include "ResourceRequest.h" 33 #include "ResourceResponse.h" 34 #include <wtf/text/CString.h> 35 #include <gst/app/gstappsrc.h> 36 #include <gst/pbutils/missing-plugins.h> 37 38 using namespace WebCore; 39 40 class StreamingClient : public ResourceHandleClient { 41 WTF_MAKE_NONCOPYABLE(StreamingClient); 42 public: 43 StreamingClient(WebKitWebSrc*); 44 virtual ~StreamingClient(); 45 46 virtual void willSendRequest(ResourceHandle*, ResourceRequest&, const ResourceResponse&); 47 virtual void didReceiveResponse(ResourceHandle*, const ResourceResponse&); 48 virtual void didReceiveData(ResourceHandle*, const char*, int, int); 49 virtual void didFinishLoading(ResourceHandle*, double /*finishTime*/); 50 virtual void didFail(ResourceHandle*, const ResourceError&); 51 virtual void wasBlocked(ResourceHandle*); 52 virtual void cannotShowURL(ResourceHandle*); 53 54 private: 55 WebKitWebSrc* m_src; 56 }; 57 58 #define WEBKIT_WEB_SRC_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE((obj), WEBKIT_TYPE_WEB_SRC, WebKitWebSrcPrivate)) 59 struct _WebKitWebSrcPrivate { 60 GstAppSrc* appsrc; 61 GstPad* srcpad; 62 gchar* uri; 63 64 RefPtr<WebCore::Frame> frame; 65 66 StreamingClient* client; 67 RefPtr<ResourceHandle> resourceHandle; 68 69 guint64 offset; 70 guint64 size; 71 gboolean seekable; 72 gboolean paused; 73 74 guint64 requestedOffset; 75 76 guint needDataID; 77 guint enoughDataID; 78 guint seekID; 79 80 // icecast stuff 81 gboolean iradioMode; 82 gchar* iradioName; 83 gchar* iradioGenre; 84 gchar* iradioUrl; 85 gchar* iradioTitle; 86 87 // TRUE if appsrc's version is >= 0.10.27, see 88 // https://bugzilla.gnome.org/show_bug.cgi?id=609423 89 gboolean haveAppSrc27; 90 }; 91 92 enum { 93 PROP_IRADIO_MODE = 1, 94 PROP_IRADIO_NAME, 95 PROP_IRADIO_GENRE, 96 PROP_IRADIO_URL, 97 PROP_IRADIO_TITLE, 98 PROP_LOCATION 99 }; 100 101 static GstStaticPadTemplate srcTemplate = GST_STATIC_PAD_TEMPLATE("src", 102 GST_PAD_SRC, 103 GST_PAD_ALWAYS, 104 GST_STATIC_CAPS_ANY); 105 106 GST_DEBUG_CATEGORY_STATIC(webkit_web_src_debug); 107 #define GST_CAT_DEFAULT webkit_web_src_debug 108 109 static void webKitWebSrcUriHandlerInit(gpointer gIface, 110 gpointer ifaceData); 111 112 static void webKitWebSrcFinalize(GObject* object); 113 static void webKitWebSrcSetProperty(GObject* object, guint propID, const GValue* value, GParamSpec* pspec); 114 static void webKitWebSrcGetProperty(GObject* object, guint propID, GValue* value, GParamSpec* pspec); 115 static GstStateChangeReturn webKitWebSrcChangeState(GstElement* element, GstStateChange transition); 116 static gboolean webKitWebSrcQuery(GstPad* pad, GstQuery* query); 117 118 static void webKitWebSrcNeedDataCb(GstAppSrc* appsrc, guint length, gpointer userData); 119 static void webKitWebSrcEnoughDataCb(GstAppSrc* appsrc, gpointer userData); 120 static gboolean webKitWebSrcSeekDataCb(GstAppSrc* appsrc, guint64 offset, gpointer userData); 121 122 static void webKitWebSrcStop(WebKitWebSrc* src, bool seeking); 123 static gboolean webKitWebSrcSetUri(GstURIHandler*, const gchar*); 124 static const gchar* webKitWebSrcGetUri(GstURIHandler*); 125 126 static GstAppSrcCallbacks appsrcCallbacks = { 127 webKitWebSrcNeedDataCb, 128 webKitWebSrcEnoughDataCb, 129 webKitWebSrcSeekDataCb, 130 { 0 } 131 }; 132 133 static void doInit(GType gtype) 134 { 135 static const GInterfaceInfo uriHandlerInfo = { 136 webKitWebSrcUriHandlerInit, 137 0, 0 138 }; 139 140 GST_DEBUG_CATEGORY_INIT(webkit_web_src_debug, "webkitwebsrc", 0, "websrc element"); 141 g_type_add_interface_static(gtype, GST_TYPE_URI_HANDLER, 142 &uriHandlerInfo); 143 } 144 145 GST_BOILERPLATE_FULL(WebKitWebSrc, webkit_web_src, GstBin, GST_TYPE_BIN, doInit); 146 147 static void webkit_web_src_base_init(gpointer klass) 148 { 149 GstElementClass* eklass = GST_ELEMENT_CLASS(klass); 150 151 gst_element_class_add_pad_template(eklass, 152 gst_static_pad_template_get(&srcTemplate)); 153 gst_element_class_set_details_simple(eklass, 154 (gchar*) "WebKit Web source element", 155 (gchar*) "Source", 156 (gchar*) "Handles HTTP/HTTPS uris", 157 (gchar*) "Sebastian Drge <sebastian.droege (at) collabora.co.uk>"); 158 } 159 160 static void webkit_web_src_class_init(WebKitWebSrcClass* klass) 161 { 162 GObjectClass* oklass = G_OBJECT_CLASS(klass); 163 GstElementClass* eklass = GST_ELEMENT_CLASS(klass); 164 165 oklass->finalize = webKitWebSrcFinalize; 166 oklass->set_property = webKitWebSrcSetProperty; 167 oklass->get_property = webKitWebSrcGetProperty; 168 169 // icecast stuff 170 g_object_class_install_property(oklass, 171 PROP_IRADIO_MODE, 172 g_param_spec_boolean("iradio-mode", 173 "iradio-mode", 174 "Enable internet radio mode (extraction of shoutcast/icecast metadata)", 175 FALSE, 176 (GParamFlags) (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS))); 177 178 g_object_class_install_property(oklass, 179 PROP_IRADIO_NAME, 180 g_param_spec_string("iradio-name", 181 "iradio-name", 182 "Name of the stream", 183 0, 184 (GParamFlags) (G_PARAM_READABLE | G_PARAM_STATIC_STRINGS))); 185 186 g_object_class_install_property(oklass, 187 PROP_IRADIO_GENRE, 188 g_param_spec_string("iradio-genre", 189 "iradio-genre", 190 "Genre of the stream", 191 0, 192 (GParamFlags) (G_PARAM_READABLE | G_PARAM_STATIC_STRINGS))); 193 194 g_object_class_install_property(oklass, 195 PROP_IRADIO_URL, 196 g_param_spec_string("iradio-url", 197 "iradio-url", 198 "Homepage URL for radio stream", 199 0, 200 (GParamFlags) (G_PARAM_READABLE | G_PARAM_STATIC_STRINGS))); 201 202 g_object_class_install_property(oklass, 203 PROP_IRADIO_TITLE, 204 g_param_spec_string("iradio-title", 205 "iradio-title", 206 "Name of currently playing song", 207 0, 208 (GParamFlags) (G_PARAM_READABLE | G_PARAM_STATIC_STRINGS))); 209 210 211 /* Allows setting the uri using the 'location' property, which is used 212 * for example by gst_element_make_from_uri() */ 213 g_object_class_install_property(oklass, 214 PROP_LOCATION, 215 g_param_spec_string("location", 216 "location", 217 "Location to read from", 218 0, 219 (GParamFlags) (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS))); 220 eklass->change_state = webKitWebSrcChangeState; 221 222 g_type_class_add_private(klass, sizeof(WebKitWebSrcPrivate)); 223 } 224 225 static void webkit_web_src_init(WebKitWebSrc* src, 226 WebKitWebSrcClass* gKlass) 227 { 228 GstPadTemplate* padTemplate = gst_static_pad_template_get(&srcTemplate); 229 GstPad* targetpad; 230 WebKitWebSrcPrivate* priv = WEBKIT_WEB_SRC_GET_PRIVATE(src); 231 232 src->priv = priv; 233 234 priv->client = new StreamingClient(src); 235 236 priv->srcpad = gst_ghost_pad_new_no_target_from_template("src", 237 padTemplate); 238 239 gst_element_add_pad(GST_ELEMENT(src), priv->srcpad); 240 gst_pad_set_query_function(priv->srcpad, webKitWebSrcQuery); 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 targetpad = gst_element_get_static_pad(GST_ELEMENT(priv->appsrc), "src"); 254 gst_ghost_pad_set_target(GST_GHOST_PAD(priv->srcpad), targetpad); 255 gst_object_unref(targetpad); 256 257 gst_app_src_set_callbacks(priv->appsrc, &appsrcCallbacks, src, 0); 258 gst_app_src_set_emit_signals(priv->appsrc, FALSE); 259 gst_app_src_set_stream_type(priv->appsrc, GST_APP_STREAM_TYPE_SEEKABLE); 260 261 // 512k is a abitrary number but we should choose a value 262 // here to not pause/unpause the SoupMessage too often and 263 // to make sure there's always some data available for 264 // GStreamer to handle. 265 gst_app_src_set_max_bytes(priv->appsrc, 512 * 1024); 266 267 // Emit the need-data signal if the queue contains less 268 // than 20% of data. Without this the need-data signal 269 // is emitted when the queue is empty, we then dispatch 270 // the soup message unpausing to the main loop and from 271 // there unpause the soup message. This already takes 272 // quite some time and libsoup even needs some more time 273 // to actually provide data again. If we do all this 274 // already if the queue is 20% empty, it's much more 275 // likely that libsoup already provides new data before 276 // the queue is really empty. 277 // This might need tweaking for ports not using libsoup. 278 if (priv->haveAppSrc27) 279 g_object_set(priv->appsrc, "min-percent", 20, NULL); 280 281 webKitWebSrcStop(src, false); 282 } 283 284 static void webKitWebSrcFinalize(GObject* object) 285 { 286 WebKitWebSrc* src = WEBKIT_WEB_SRC(object); 287 WebKitWebSrcPrivate* priv = src->priv; 288 289 delete priv->client; 290 291 g_free(priv->uri); 292 293 GST_CALL_PARENT(G_OBJECT_CLASS, finalize, ((GObject* )(src))); 294 } 295 296 static void webKitWebSrcSetProperty(GObject* object, guint propID, const GValue* value, GParamSpec* pspec) 297 { 298 WebKitWebSrc* src = WEBKIT_WEB_SRC(object); 299 WebKitWebSrcPrivate* priv = src->priv; 300 301 switch (propID) { 302 case PROP_IRADIO_MODE: 303 priv->iradioMode = g_value_get_boolean(value); 304 break; 305 case PROP_LOCATION: 306 webKitWebSrcSetUri(reinterpret_cast<GstURIHandler*>(src), g_value_get_string(value)); 307 break; 308 default: 309 G_OBJECT_WARN_INVALID_PROPERTY_ID(object, propID, pspec); 310 break; 311 } 312 } 313 314 static void webKitWebSrcGetProperty(GObject* object, guint propID, GValue* value, GParamSpec* pspec) 315 { 316 WebKitWebSrc* src = WEBKIT_WEB_SRC(object); 317 WebKitWebSrcPrivate* priv = src->priv; 318 319 switch (propID) { 320 case PROP_IRADIO_MODE: 321 g_value_set_boolean(value, priv->iradioMode); 322 break; 323 case PROP_IRADIO_NAME: 324 g_value_set_string(value, priv->iradioName); 325 break; 326 case PROP_IRADIO_GENRE: 327 g_value_set_string(value, priv->iradioGenre); 328 break; 329 case PROP_IRADIO_URL: 330 g_value_set_string(value, priv->iradioUrl); 331 break; 332 case PROP_IRADIO_TITLE: 333 g_value_set_string(value, priv->iradioTitle); 334 break; 335 case PROP_LOCATION: 336 g_value_set_string(value, webKitWebSrcGetUri(reinterpret_cast<GstURIHandler*>(src))); 337 break; 338 default: 339 G_OBJECT_WARN_INVALID_PROPERTY_ID(object, propID, pspec); 340 break; 341 } 342 } 343 344 345 static void webKitWebSrcStop(WebKitWebSrc* src, bool seeking) 346 { 347 WebKitWebSrcPrivate* priv = src->priv; 348 349 if (priv->resourceHandle) { 350 priv->resourceHandle->cancel(); 351 priv->resourceHandle.release(); 352 } 353 priv->resourceHandle = 0; 354 355 if (priv->frame && !seeking) 356 priv->frame.release(); 357 358 GST_OBJECT_LOCK(src); 359 if (priv->needDataID) 360 g_source_remove(priv->needDataID); 361 priv->needDataID = 0; 362 363 if (priv->enoughDataID) 364 g_source_remove(priv->enoughDataID); 365 priv->enoughDataID = 0; 366 367 if (priv->seekID) 368 g_source_remove(priv->seekID); 369 priv->seekID = 0; 370 371 priv->paused = FALSE; 372 GST_OBJECT_UNLOCK(src); 373 374 g_free(priv->iradioName); 375 priv->iradioName = 0; 376 377 g_free(priv->iradioGenre); 378 priv->iradioGenre = 0; 379 380 g_free(priv->iradioUrl); 381 priv->iradioUrl = 0; 382 383 g_free(priv->iradioTitle); 384 priv->iradioTitle = 0; 385 386 if (priv->appsrc) { 387 gst_app_src_set_caps(priv->appsrc, 0); 388 if (!seeking) 389 gst_app_src_set_size(priv->appsrc, -1); 390 } 391 392 priv->offset = 0; 393 priv->seekable = FALSE; 394 395 if (!seeking) { 396 priv->size = 0; 397 priv->requestedOffset = 0; 398 } 399 400 GST_DEBUG_OBJECT(src, "Stopped request"); 401 } 402 403 static bool webKitWebSrcStart(WebKitWebSrc* src) 404 { 405 WebKitWebSrcPrivate* priv = src->priv; 406 407 if (!priv->uri) { 408 GST_ERROR_OBJECT(src, "No URI provided"); 409 return false; 410 } 411 412 KURL url = KURL(KURL(), priv->uri); 413 414 ResourceRequest request(url); 415 request.setTargetType(ResourceRequestBase::TargetIsMedia); 416 request.setAllowCookies(true); 417 418 NetworkingContext* context = 0; 419 if (priv->frame) { 420 Document* document = priv->frame->document(); 421 if (document) 422 request.setHTTPReferrer(document->documentURI()); 423 424 FrameLoader* loader = priv->frame->loader(); 425 if (loader) { 426 loader->addExtraFieldsToSubresourceRequest(request); 427 context = loader->networkingContext(); 428 } 429 } 430 431 // Let Apple web servers know we want to access their nice movie trailers. 432 if (!g_ascii_strcasecmp("movies.apple.com", url.host().utf8().data()) 433 || !g_ascii_strcasecmp("trailers.apple.com", url.host().utf8().data())) 434 request.setHTTPUserAgent("Quicktime/7.6.6"); 435 436 if (priv->requestedOffset) { 437 GOwnPtr<gchar> val; 438 439 val.set(g_strdup_printf("bytes=%" G_GUINT64_FORMAT "-", priv->requestedOffset)); 440 request.setHTTPHeaderField("Range", val.get()); 441 } 442 443 if (priv->iradioMode) 444 request.setHTTPHeaderField("icy-metadata", "1"); 445 446 // Needed to use DLNA streaming servers 447 request.setHTTPHeaderField("transferMode.dlna", "Streaming"); 448 449 priv->resourceHandle = ResourceHandle::create(context, request, priv->client, false, false); 450 if (!priv->resourceHandle) { 451 GST_ERROR_OBJECT(src, "Failed to create ResourceHandle"); 452 return false; 453 } 454 455 GST_DEBUG_OBJECT(src, "Started request"); 456 457 return true; 458 } 459 460 static GstStateChangeReturn webKitWebSrcChangeState(GstElement* element, GstStateChange transition) 461 { 462 GstStateChangeReturn ret = GST_STATE_CHANGE_SUCCESS; 463 WebKitWebSrc* src = WEBKIT_WEB_SRC(element); 464 WebKitWebSrcPrivate* priv = src->priv; 465 466 switch (transition) { 467 case GST_STATE_CHANGE_NULL_TO_READY: 468 if (!priv->appsrc) { 469 gst_element_post_message(element, 470 gst_missing_element_message_new(element, "appsrc")); 471 GST_ELEMENT_ERROR(src, CORE, MISSING_PLUGIN, (0), ("no appsrc")); 472 return GST_STATE_CHANGE_FAILURE; 473 } 474 break; 475 default: 476 break; 477 } 478 479 ret = GST_ELEMENT_CLASS(parent_class)->change_state(element, transition); 480 if (G_UNLIKELY(ret == GST_STATE_CHANGE_FAILURE)) { 481 GST_DEBUG_OBJECT(src, "State change failed"); 482 return ret; 483 } 484 485 switch (transition) { 486 case GST_STATE_CHANGE_READY_TO_PAUSED: 487 GST_DEBUG_OBJECT(src, "READY->PAUSED"); 488 if (!webKitWebSrcStart(src)) 489 ret = GST_STATE_CHANGE_FAILURE; 490 break; 491 case GST_STATE_CHANGE_PAUSED_TO_READY: 492 GST_DEBUG_OBJECT(src, "PAUSED->READY"); 493 webKitWebSrcStop(src, false); 494 break; 495 default: 496 break; 497 } 498 499 return ret; 500 } 501 502 static gboolean webKitWebSrcQuery(GstPad* pad, GstQuery* query) 503 { 504 GRefPtr<GstElement> src = adoptGRef(gst_pad_get_parent_element(pad)); 505 WebKitWebSrc* webkitSrc = WEBKIT_WEB_SRC(src.get()); 506 gboolean result = FALSE; 507 508 switch (GST_QUERY_TYPE(query)) { 509 case GST_QUERY_DURATION: 510 { 511 GstFormat format; 512 513 gst_query_parse_duration(query, &format, NULL); 514 515 GST_DEBUG_OBJECT(webkitSrc, "duration query in format %s", gst_format_get_name(format)); 516 if ((format == GST_FORMAT_BYTES) && (webkitSrc->priv->size > 0)) { 517 gst_query_set_duration(query, format, webkitSrc->priv->size); 518 result = TRUE; 519 } 520 break; 521 } 522 case GST_QUERY_URI: 523 { 524 gst_query_set_uri(query, webkitSrc->priv->uri); 525 result = TRUE; 526 break; 527 } 528 default: 529 break; 530 } 531 532 if (!result) 533 result = gst_pad_query_default(pad, query); 534 535 return result; 536 } 537 538 // uri handler interface 539 540 static GstURIType webKitWebSrcUriGetType(void) 541 { 542 return GST_URI_SRC; 543 } 544 545 static gchar** webKitWebSrcGetProtocols(void) 546 { 547 static gchar* protocols[] = {(gchar*) "http", (gchar*) "https", 0 }; 548 549 return protocols; 550 } 551 552 static const gchar* webKitWebSrcGetUri(GstURIHandler* handler) 553 { 554 WebKitWebSrc* src = WEBKIT_WEB_SRC(handler); 555 WebKitWebSrcPrivate* priv = src->priv; 556 557 return priv->uri; 558 } 559 560 static gboolean webKitWebSrcSetUri(GstURIHandler* handler, const gchar* uri) 561 { 562 WebKitWebSrc* src = WEBKIT_WEB_SRC(handler); 563 WebKitWebSrcPrivate* priv = src->priv; 564 565 if (GST_STATE(src) >= GST_STATE_PAUSED) { 566 GST_ERROR_OBJECT(src, "URI can only be set in states < PAUSED"); 567 return FALSE; 568 } 569 570 g_free(priv->uri); 571 priv->uri = 0; 572 573 if (!uri) 574 return TRUE; 575 576 KURL url(KURL(), uri); 577 578 if (!url.isValid() || !url.protocolInHTTPFamily()) { 579 GST_ERROR_OBJECT(src, "Invalid URI '%s'", uri); 580 return FALSE; 581 } 582 583 priv->uri = g_strdup(url.string().utf8().data()); 584 return TRUE; 585 } 586 587 static void webKitWebSrcUriHandlerInit(gpointer gIface, gpointer ifaceData) 588 { 589 GstURIHandlerInterface* iface = (GstURIHandlerInterface *) gIface; 590 591 iface->get_type = webKitWebSrcUriGetType; 592 iface->get_protocols = webKitWebSrcGetProtocols; 593 iface->get_uri = webKitWebSrcGetUri; 594 iface->set_uri = webKitWebSrcSetUri; 595 } 596 597 // appsrc callbacks 598 599 static gboolean webKitWebSrcNeedDataMainCb(WebKitWebSrc* src) 600 { 601 WebKitWebSrcPrivate* priv = src->priv; 602 603 priv->resourceHandle->setDefersLoading(false); 604 605 GST_OBJECT_LOCK(src); 606 priv->paused = FALSE; 607 priv->needDataID = 0; 608 GST_OBJECT_UNLOCK(src); 609 return FALSE; 610 } 611 612 static void webKitWebSrcNeedDataCb(GstAppSrc* appsrc, guint length, gpointer userData) 613 { 614 WebKitWebSrc* src = WEBKIT_WEB_SRC(userData); 615 WebKitWebSrcPrivate* priv = src->priv; 616 617 GST_DEBUG_OBJECT(src, "Need more data: %u", length); 618 619 GST_OBJECT_LOCK(src); 620 if (priv->needDataID || !priv->paused) { 621 GST_OBJECT_UNLOCK(src); 622 return; 623 } 624 625 priv->needDataID = g_timeout_add_full(G_PRIORITY_DEFAULT, 0, (GSourceFunc) webKitWebSrcNeedDataMainCb, gst_object_ref(src), (GDestroyNotify) gst_object_unref); 626 GST_OBJECT_UNLOCK(src); 627 } 628 629 static gboolean webKitWebSrcEnoughDataMainCb(WebKitWebSrc* src) 630 { 631 WebKitWebSrcPrivate* priv = src->priv; 632 633 priv->resourceHandle->setDefersLoading(true); 634 635 GST_OBJECT_LOCK(src); 636 priv->paused = TRUE; 637 priv->enoughDataID = 0; 638 GST_OBJECT_UNLOCK(src); 639 640 return FALSE; 641 } 642 643 static void webKitWebSrcEnoughDataCb(GstAppSrc* appsrc, gpointer userData) 644 { 645 WebKitWebSrc* src = WEBKIT_WEB_SRC(userData); 646 WebKitWebSrcPrivate* priv = src->priv; 647 648 GST_DEBUG_OBJECT(src, "Have enough data"); 649 650 GST_OBJECT_LOCK(src); 651 if (priv->enoughDataID || priv->paused) { 652 GST_OBJECT_UNLOCK(src); 653 return; 654 } 655 656 priv->enoughDataID = g_timeout_add_full(G_PRIORITY_DEFAULT, 0, (GSourceFunc) webKitWebSrcEnoughDataMainCb, gst_object_ref(src), (GDestroyNotify) gst_object_unref); 657 GST_OBJECT_UNLOCK(src); 658 } 659 660 static gboolean webKitWebSrcSeekMainCb(WebKitWebSrc* src) 661 { 662 webKitWebSrcStop(src, true); 663 webKitWebSrcStart(src); 664 665 return FALSE; 666 } 667 668 static gboolean webKitWebSrcSeekDataCb(GstAppSrc* appsrc, guint64 offset, gpointer userData) 669 { 670 WebKitWebSrc* src = WEBKIT_WEB_SRC(userData); 671 WebKitWebSrcPrivate* priv = src->priv; 672 673 GST_DEBUG_OBJECT(src, "Seeking to offset: %" G_GUINT64_FORMAT, offset); 674 if (offset == priv->offset) 675 return TRUE; 676 677 if (!priv->seekable) 678 return FALSE; 679 if (offset > priv->size) 680 return FALSE; 681 682 GST_DEBUG_OBJECT(src, "Doing range-request seek"); 683 priv->requestedOffset = offset; 684 685 GST_OBJECT_LOCK(src); 686 if (priv->seekID) 687 g_source_remove(priv->seekID); 688 priv->seekID = g_timeout_add_full(G_PRIORITY_DEFAULT, 0, (GSourceFunc) webKitWebSrcSeekMainCb, gst_object_ref(src), (GDestroyNotify) gst_object_unref); 689 GST_OBJECT_UNLOCK(src); 690 691 return TRUE; 692 } 693 694 void webKitWebSrcSetFrame(WebKitWebSrc* src, WebCore::Frame* frame) 695 { 696 WebKitWebSrcPrivate* priv = src->priv; 697 698 priv->frame = frame; 699 } 700 701 StreamingClient::StreamingClient(WebKitWebSrc* src) : m_src(src) 702 { 703 704 } 705 706 StreamingClient::~StreamingClient() 707 { 708 709 } 710 711 void StreamingClient::willSendRequest(ResourceHandle*, ResourceRequest&, const ResourceResponse&) 712 { 713 } 714 715 void StreamingClient::didReceiveResponse(ResourceHandle*, const ResourceResponse& response) 716 { 717 WebKitWebSrcPrivate* priv = m_src->priv; 718 719 GST_DEBUG_OBJECT(m_src, "Received response: %d", response.httpStatusCode()); 720 721 // If we seeked we need 206 == PARTIAL_CONTENT 722 if (priv->requestedOffset && response.httpStatusCode() != 206) { 723 GST_ELEMENT_ERROR(m_src, RESOURCE, READ, (0), (0)); 724 gst_app_src_end_of_stream(priv->appsrc); 725 webKitWebSrcStop(m_src, false); 726 return; 727 } 728 729 long long length = response.expectedContentLength(); 730 if (length > 0) { 731 length += priv->requestedOffset; 732 gst_app_src_set_size(priv->appsrc, length); 733 if (!priv->haveAppSrc27) { 734 gst_segment_set_duration(&GST_BASE_SRC(priv->appsrc)->segment, GST_FORMAT_BYTES, length); 735 gst_element_post_message(GST_ELEMENT(priv->appsrc), 736 gst_message_new_duration(GST_OBJECT(priv->appsrc), 737 GST_FORMAT_BYTES, length)); 738 } 739 } 740 741 priv->size = length >= 0 ? length : 0; 742 priv->seekable = length > 0 && g_ascii_strcasecmp("none", response.httpHeaderField("Accept-Ranges").utf8().data()); 743 744 // icecast stuff 745 String value = response.httpHeaderField("icy-metaint"); 746 if (!value.isEmpty()) { 747 gchar* endptr = 0; 748 gint64 icyMetaInt = g_ascii_strtoll(value.utf8().data(), &endptr, 10); 749 750 if (endptr && *endptr == '\0' && icyMetaInt > 0) { 751 GstCaps* caps = gst_caps_new_simple("application/x-icy", "metadata-interval", G_TYPE_INT, (gint) icyMetaInt, NULL); 752 753 gst_app_src_set_caps(priv->appsrc, caps); 754 gst_caps_unref(caps); 755 } 756 } 757 758 GstTagList* tags = gst_tag_list_new(); 759 value = response.httpHeaderField("icy-name"); 760 if (!value.isEmpty()) { 761 g_free(priv->iradioName); 762 priv->iradioName = g_strdup(value.utf8().data()); 763 g_object_notify(G_OBJECT(m_src), "iradio-name"); 764 gst_tag_list_add(tags, GST_TAG_MERGE_REPLACE, GST_TAG_ORGANIZATION, priv->iradioName, NULL); 765 } 766 value = response.httpHeaderField("icy-genre"); 767 if (!value.isEmpty()) { 768 g_free(priv->iradioGenre); 769 priv->iradioGenre = g_strdup(value.utf8().data()); 770 g_object_notify(G_OBJECT(m_src), "iradio-genre"); 771 gst_tag_list_add(tags, GST_TAG_MERGE_REPLACE, GST_TAG_GENRE, priv->iradioGenre, NULL); 772 } 773 value = response.httpHeaderField("icy-url"); 774 if (!value.isEmpty()) { 775 g_free(priv->iradioUrl); 776 priv->iradioUrl = g_strdup(value.utf8().data()); 777 g_object_notify(G_OBJECT(m_src), "iradio-url"); 778 gst_tag_list_add(tags, GST_TAG_MERGE_REPLACE, GST_TAG_LOCATION, priv->iradioUrl, NULL); 779 } 780 value = response.httpHeaderField("icy-title"); 781 if (!value.isEmpty()) { 782 g_free(priv->iradioTitle); 783 priv->iradioTitle = g_strdup(value.utf8().data()); 784 g_object_notify(G_OBJECT(m_src), "iradio-title"); 785 gst_tag_list_add(tags, GST_TAG_MERGE_REPLACE, GST_TAG_TITLE, priv->iradioTitle, NULL); 786 } 787 788 if (gst_tag_list_is_empty(tags)) 789 gst_tag_list_free(tags); 790 else 791 gst_element_found_tags_for_pad(GST_ELEMENT(m_src), m_src->priv->srcpad, tags); 792 } 793 794 void StreamingClient::didReceiveData(ResourceHandle* handle, const char* data, int length, int encodedDataLength) 795 { 796 WebKitWebSrcPrivate* priv = m_src->priv; 797 798 GST_LOG_OBJECT(m_src, "Have %d bytes of data", length); 799 800 if (priv->seekID || handle != priv->resourceHandle) { 801 GST_DEBUG_OBJECT(m_src, "Seek in progress, ignoring data"); 802 return; 803 } 804 805 GstBuffer* buffer = gst_buffer_new_and_alloc(length); 806 807 memcpy(GST_BUFFER_DATA(buffer), data, length); 808 GST_BUFFER_OFFSET(buffer) = priv->offset; 809 priv->offset += length; 810 GST_BUFFER_OFFSET_END(buffer) = priv->offset; 811 812 GstFlowReturn ret = gst_app_src_push_buffer(priv->appsrc, buffer); 813 if (ret != GST_FLOW_OK && ret != GST_FLOW_UNEXPECTED) 814 GST_ELEMENT_ERROR(m_src, CORE, FAILED, (0), (0)); 815 } 816 817 void StreamingClient::didFinishLoading(ResourceHandle*, double) 818 { 819 WebKitWebSrcPrivate* priv = m_src->priv; 820 821 GST_DEBUG_OBJECT(m_src, "Have EOS"); 822 823 if (!priv->seekID) 824 gst_app_src_end_of_stream(m_src->priv->appsrc); 825 } 826 827 void StreamingClient::didFail(ResourceHandle*, const ResourceError& error) 828 { 829 GST_ERROR_OBJECT(m_src, "Have failure: %s", error.localizedDescription().utf8().data()); 830 GST_ELEMENT_ERROR(m_src, RESOURCE, FAILED, ("%s", error.localizedDescription().utf8().data()), (0)); 831 gst_app_src_end_of_stream(m_src->priv->appsrc); 832 } 833 834 void StreamingClient::wasBlocked(ResourceHandle*) 835 { 836 GST_ERROR_OBJECT(m_src, "Request was blocked"); 837 GST_ELEMENT_ERROR(m_src, RESOURCE, OPEN_READ, ("Access to \"%s\" was blocked", m_src->priv->uri), (0)); 838 } 839 840 void StreamingClient::cannotShowURL(ResourceHandle*) 841 { 842 GST_ERROR_OBJECT(m_src, "Cannot show URL"); 843 GST_ELEMENT_ERROR(m_src, RESOURCE, OPEN_READ, ("Can't show \"%s\"", m_src->priv->uri), (0)); 844 } 845 846 #endif // USE(GSTREAMER) 847 848