mozillasrc.cpp
Go to the documentation of this file.
1 /* GStreamer
2  * Copyright (C) <2008> Pioneers of the Inevitable <songbird@songbirdnest.com>
3  *
4  * Authors: Michael Smith <msmith@songbirdnest.com>
5  *
6  * This library is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU Library General Public
8  * License as published by the Free Software Foundation; either
9  * version 2 of the License, or (at your option) any later version.
10  *
11  * This library is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14  * Library General Public License for more
15  */
16 #ifdef HAVE_CONFIG_H
17 #include "config.h"
18 #endif
19 
20 /* Must be before any other mozilla headers */
21 #include "mozilla-config.h"
22 
23 #include <gst/gst.h>
24 #include <gst/base/gstpushsrc.h>
25 #include <gst/base/gstadapter.h>
26 
27 #include <nsStringAPI.h>
28 #include <nsNetUtil.h>
29 #include <nsComponentManagerUtils.h>
30 #include <nsIProtocolHandler.h>
31 #include <nsIComponentRegistrar.h>
32 #include <nsCOMPtr.h>
33 #include <nsISupportsPrimitives.h>
34 #include <nsIResumableChannel.h>
35 #include <nsIRunnable.h>
36 #include <nsIHttpHeaderVisitor.h>
37 #include <nsIHttpEventSink.h>
38 #include <nsThreadUtils.h>
39 #include <nsIWindowWatcher.h>
40 #include <nsIAuthPrompt.h>
41 
42 #include "mozillaplugin.h"
43 
46 
47 class StreamListener;
48 
50  GstPushSrc element;
51 
52  gint64 content_size;
53 
54  gboolean eos;
55  gboolean flushing;
56 
57  gboolean iradio_mode; // Are we in internet radio mode?
58  GstCaps *icy_caps; // caps to use (only in iradio mode)
59  gboolean is_shoutcast_server; // Are we dealing with a shoutcast server?
60  gboolean shoutcast_headers_read; // If so, have we read the response headers?
61 
62  gint64 current_position; // Current offset in the stream, in bytes.
63  gboolean is_seekable; // This is true until we know seeking is not
64  // supported (after we've seen the response)
65  gboolean is_cancelled; // TRUE if we cancelled the request (and hence
66  // shouldn't treat the request ending as EOS)
67 
68  /* URI as a UTF-8 string */
69  const gchar *location;
70  /* URI for location */
71  nsCOMPtr<nsIURI> uri;
72 
73  /* Our input channel */
74  nsCOMPtr<nsIChannel> channel;
75  gboolean suspended; /* Is reading from the channel currently suspended? */
76 
77  /* Simple async queue to decouple mozilla (which is reading
78  * from the main thread) and the streaming thread run by basesrc.
79  * We keep it limited in size by suspending the request if we get
80  * too full */
81  GQueue *queue;
82  gint queue_size;
83  GMutex *queue_lock;
84  GCond *queue_cond;
85 };
86 
88  GstPushSrcClass parent_class;
89 };
90 
91 GST_DEBUG_CATEGORY_STATIC (mozillasrc_debug);
92 #define GST_CAT_DEFAULT mozillasrc_debug
93 
94 static const GstElementDetails gst_mozilla_src_details =
95 GST_ELEMENT_DETAILS ((gchar *)"Mozilla nsIInputStream source",
96  (gchar *)"Source/Network",
97  (gchar *)"Receive data from a hosting mozilla "
98  "application Mozilla's I/O APIs",
99  (gchar *)"Pioneers of the Inevitable <songbird@songbirdnest.com");
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 enum
107 {
111 };
112 
113 static void gst_mozilla_src_uri_handler_init (gpointer g_iface,
114  gpointer iface_data);
115 
116 static void gst_mozilla_src_finalize (GObject * gobject);
117 static void gst_mozilla_src_set_property (GObject * object, guint prop_id,
118  const GValue * value, GParamSpec * pspec);
119 static void gst_mozilla_src_get_property (GObject * object, guint prop_id,
120  GValue * value, GParamSpec * pspec);
121 
122 static GstFlowReturn gst_mozilla_src_create (GstPushSrc * psrc,
123  GstBuffer ** outbuf);
124 static gboolean gst_mozilla_src_start (GstBaseSrc * bsrc);
125 static gboolean gst_mozilla_src_stop (GstBaseSrc * bsrc);
126 static gboolean gst_mozilla_src_get_size (GstBaseSrc * bsrc,
127  guint64 * size);
128 static gboolean gst_mozilla_src_is_seekable (GstBaseSrc * bsrc);
129 static gboolean gst_mozilla_src_do_seek (GstBaseSrc * bsrc,
130  GstSegment * segment);
131 static gboolean gst_mozilla_src_unlock(GstBaseSrc * psrc);
132 static gboolean gst_mozilla_src_unlock_stop(GstBaseSrc * psrc);
133 
134 class ResumeEvent : public nsIRunnable
135 {
136 public:
138  NS_DECL_NSIRUNNABLE
139 
140  explicit ResumeEvent (GstMozillaSrc *src)
141  {
142  mSrc = (GstMozillaSrc *)g_object_ref (src);
143  }
144 
146  {
147  g_object_unref (mSrc);
148  }
149 private:
150  GstMozillaSrc *mSrc;
151 };
152 
154  nsIRunnable)
155 
156 NS_IMETHODIMP
157 ResumeEvent::Run()
158 {
159  if (mSrc->suspended) {
160  nsCOMPtr<nsIRequest> request(do_QueryInterface(mSrc->channel));
161  if (request) {
162  GST_DEBUG_OBJECT (mSrc, "Resuming request...");
163  request->Resume();
164  mSrc->suspended = FALSE;
165  }
166  }
167 
168  return NS_OK;
169 }
170 
171 class StreamListener : public nsIStreamListener,
172  public nsIHttpHeaderVisitor,
173  public nsIInterfaceRequestor,
174  public nsIHttpEventSink
175 {
176 public:
178  NS_DECL_NSIREQUESTOBSERVER
179  NS_DECL_NSISTREAMLISTENER
180  NS_DECL_NSIHTTPHEADERVISITOR
181  NS_DECL_NSIINTERFACEREQUESTOR
182  NS_DECL_NSIHTTPEVENTSINK
183 
185  virtual ~StreamListener();
186 
187 private:
188  GstMozillaSrc *mSrc;
189  GstAdapter *mAdapter;
190 
191  void ReadHeaders (gchar *data);
192  GstBuffer *ScanForHeaders(GstBuffer *buf);
193 };
194 
196  mSrc(aSrc),
197  mAdapter(NULL)
198 {
199 }
200 
202 {
203  if (mAdapter)
204  g_object_unref (mAdapter);
205 }
206 
208  nsIRequestObserver,
209  nsIStreamListener,
210  nsIHttpHeaderVisitor,
212  nsIHttpEventSink)
213 
214 NS_IMETHODIMP
215 StreamListener::GetInterface(const nsIID &aIID, void **aResult)
216 {
217  if (aIID.Equals(NS_GET_IID(nsIAuthPrompt))) {
218  nsCOMPtr<nsIWindowWatcher> wwatch(
219  do_GetService(NS_WINDOWWATCHER_CONTRACTID));
220  return wwatch->GetNewAuthPrompter(NULL, (nsIAuthPrompt**)aResult);
221  }
222  else if (aIID.Equals(NS_GET_IID(nsIHttpEventSink))) {
223  return QueryInterface(aIID, aResult);
224  }
225 
226  return NS_ERROR_NO_INTERFACE;
227 }
228 
229 NS_IMETHODIMP
230 StreamListener::OnRedirect(nsIHttpChannel *httpChannel, nsIChannel *newChannel)
231 {
232  nsCOMPtr<nsIChannel> channel(do_QueryInterface(newChannel));
233 
234  GST_DEBUG_OBJECT (mSrc, "Redirecting, got new channel");
235 
236  mSrc->channel = channel;
237  mSrc->suspended = FALSE;
238 
239  return NS_OK;
240 }
241 
242 NS_IMETHODIMP
243 StreamListener::VisitHeader(const nsACString &header, const nsACString &value)
244 {
245  // If we see any headers, it's not shoutcast
246  mSrc->is_shoutcast_server = FALSE;
247 
248  return NS_OK;
249 }
250 
251 NS_IMETHODIMP
252 StreamListener::OnStartRequest(nsIRequest *req, nsISupports *ctxt)
253 {
254  nsCOMPtr<nsIHttpChannel> httpChannel(do_QueryInterface(req));
255  nsresult rv;
256 
257  if (httpChannel) {
258  nsCAutoString acceptRangesHeader;
259  nsCAutoString icyHeader;
260  PRBool succeeded;
261 
262  if (NS_SUCCEEDED(httpChannel->GetRequestSucceeded(&succeeded))
263  && !succeeded)
264  {
265  // HTTP response is not a 2xx! Error out, then cancel the request.
266  PRUint32 responsecode;
267  nsCString responsetext;
268 
269  rv = httpChannel->GetResponseStatus(&responsecode);
270  NS_ENSURE_SUCCESS(rv, rv);
271  rv = httpChannel->GetResponseStatusText(responsetext);
272  NS_ENSURE_SUCCESS(rv, rv);
273 
274  GST_INFO_OBJECT (mSrc, "HTTP Response %d (%s)", responsecode,
275  responsetext.get());
276 
277  /* Shut down if this is our current channel (but not if we've been
278  * redirected, in which case we have a different channel now
279  */
280  nsCOMPtr<nsIChannel> channel(do_QueryInterface(req));
281  if (mSrc->channel == channel) {
282  GST_ELEMENT_ERROR (mSrc, RESOURCE, READ,
283  ("Could not read from URL %s", mSrc->location),
284  ("HTTP response code %d (%s) when fetching uri %s",
285  responsecode, responsetext.get(),
286  mSrc->location));
287 
288  req->Cancel(NS_BINDING_ABORTED);
289  }
290  return NS_OK;
291  }
292 
293  // Unfortunately, shoutcast isn't actually HTTP compliant - it sends back
294  // a non-HTTP response. Mozilla accepts this, but just gives us back the
295  // data - including the headers - as part of the body.
296  // We detect this as a response with zero headers, and then handle it
297  // specially.
298  mSrc->is_shoutcast_server = TRUE;
299  rv = httpChannel->VisitResponseHeaders(this);
300 
301  GST_DEBUG_OBJECT (mSrc, "Is shoutcast: %d", mSrc->is_shoutcast_server);
302 
303  rv = httpChannel->GetResponseHeader(NS_LITERAL_CSTRING("Accept-Ranges"),
304  acceptRangesHeader);
305  if (NS_FAILED (rv) || acceptRangesHeader.IsEmpty()) {
306  GST_DEBUG_OBJECT (mSrc, "No Accept-Ranges header in response; "
307  "setting is_seekable to false");
308  mSrc->is_seekable = FALSE;
309  } else {
310  if (acceptRangesHeader.Find (NS_LITERAL_CSTRING ("bytes")) < 0) {
311  GST_DEBUG_OBJECT (mSrc, "No 'bytes' in Accept-Ranges header; "
312  "setting is_seekable to false");
313  mSrc->is_seekable = FALSE;
314  }
315  else {
316  GST_DEBUG_OBJECT (mSrc, "Accept-Ranges header includes 'bytes' field, "
317  "seeking supported");
318  }
319  }
320 
321  if (!mSrc->is_seekable) {
322  /* There's no API for this, unfortunately */
323  ((GstBaseSrc *)mSrc)->seekable = FALSE;
324  }
325 
326  // Handle the metadata interval for non-shoutcast servers that support
327  // this (like icecast).
328  rv = httpChannel->GetResponseHeader(NS_LITERAL_CSTRING("icy-metaint"),
329  icyHeader);
330  if (NS_SUCCEEDED (rv) && !icyHeader.IsEmpty()) {
331  int metadata_interval;
332  GST_DEBUG_OBJECT (mSrc, "Received icy-metaint header, parsing it");
333 
334  metadata_interval = icyHeader.ToInteger(&rv);
335  if (NS_FAILED (rv)) {
336  GST_WARNING_OBJECT (mSrc, "Could not parse icy-metaint header");
337  }
338  else {
339  GST_DEBUG_OBJECT (mSrc, "Using icy caps with metadata interval %d",
340  metadata_interval);
341  if (mSrc->icy_caps) {
342  gst_caps_unref (mSrc->icy_caps);
343  }
344  mSrc->icy_caps = gst_caps_new_simple ("application/x-icy",
345  "metadata-interval", G_TYPE_INT, metadata_interval, NULL);
346  }
347  }
348  else {
349  GST_DEBUG_OBJECT (mSrc, "No icy-metaint header found");
350  }
351  }
352 
353  /* Unfortunately channel->GetContentLength() is only 32 bit; this is the
354  * 64 bit variant - excessively complex... */
355  nsCOMPtr<nsIPropertyBag2> properties(do_QueryInterface(mSrc->channel));
356  if (properties && mSrc->content_size == -1) {
357  PRInt64 length;
358  nsresult rv;
359  rv = properties->GetPropertyAsInt64 (NS_LITERAL_STRING ("content-length"),
360  &length);
361 
362  if (NS_SUCCEEDED(rv) && length != -1) {
363  mSrc->content_size = length;
364  GST_DEBUG_OBJECT (mSrc, "Read content length: %" G_GINT64_FORMAT,
365  mSrc->content_size);
366 
367  gst_segment_set_duration (&((GstBaseSrc *)mSrc)->segment,
368  GST_FORMAT_BYTES, mSrc->content_size);
369  gst_element_post_message (GST_ELEMENT (mSrc),
370  gst_message_new_duration (GST_OBJECT (mSrc), GST_FORMAT_BYTES,
371  mSrc->content_size));
372  }
373  else {
374  GST_DEBUG_OBJECT (mSrc, "No content-length found");
375  }
376  }
377 
378  GST_DEBUG_OBJECT (mSrc, "%p::StreamListener::OnStartRequest called; "
379  "connection made", this);
380  return NS_OK;
381 }
382 
383 NS_IMETHODIMP
384 StreamListener::OnStopRequest(nsIRequest *req, nsISupports *ctxt,
385  nsresult status)
386 {
387  GST_DEBUG_OBJECT (mSrc, "%p::StreamListener::OnStopRequest called; "
388  "connection lost", this);
389 
390  /* If we failed, turn this into an element error message. Unfortunately we
391  don't have much information at this point about what sort of failure it
392  was, so this will have to do.
393  */
394 
395  /* If we cancelled the request explicitly, we pass NS_BINDING_ABORTED as the
396  status code. This should not be treated as an error from the network stack
397  */
398  if (status != NS_BINDING_ABORTED && NS_FAILED (status)) {
399  GST_ELEMENT_ERROR (mSrc, RESOURCE, READ,
400  ("Could not read from URL %s", mSrc->location),
401  ("nsresult %d", status));
402  }
403 
404  if (!mSrc->is_cancelled)
405  {
406  GST_DEBUG_OBJECT (mSrc, "At EOS after request stopped");
407  mSrc->eos = TRUE;
408  }
409 
410  mSrc->is_cancelled = FALSE;
411 
412  return NS_OK;
413 }
414 
415 void StreamListener::ReadHeaders (gchar *data)
416 {
417  gchar **lines = g_strsplit (data, "\r\n", 0);
418  gchar **line;
419 
420  line = lines;
421  while (*line) {
422  gchar **vals = g_strsplit_set (*line, ": ", 2);
423  if (vals[0] && vals[1]) {
424  // Well formed; we can process...
425  gchar *header = vals[0];
426  gchar *value = vals[1];
427 
428  GST_DEBUG ("Read header: '%s' : '%s'", header, value);
429 
430  // There's actually only one header we're interested in right now...
431  if (g_ascii_strcasecmp (header, "icy-metaint") == 0) {
432  int metaint = g_ascii_strtoll (value, NULL, 10);
433 
434  if (metaint) {
435  GST_DEBUG ("icy-metaint read: %d", metaint);
436  if (mSrc->icy_caps)
437  gst_caps_unref (mSrc->icy_caps);
438  mSrc->icy_caps = gst_caps_new_simple ("application/x-icy",
439  "metadata-interval", G_TYPE_INT, metaint, NULL);
440  }
441  }
442  }
443 
444  g_strfreev (vals);
445  line++;
446  }
447 
448  g_strfreev (lines);
449 }
450 
451 GstBuffer *StreamListener::ScanForHeaders(GstBuffer *buf)
452 {
453  const gchar *data;
454  gchar *end;
455  int len;
456 
457  if (!mAdapter)
458  mAdapter = gst_adapter_new();
459  gst_adapter_push (mAdapter, buf);
460 
461  len = gst_adapter_available (mAdapter);
462  data = (gchar *)gst_adapter_peek (mAdapter, len);
463 
464  end = g_strrstr_len (data, len, "\r\n\r\n");
465 
466  if (!end)
467  return NULL;
468 
469  // Throw in a null terminator after the final header (overwriting the
470  // second '\r' in the terminating '\r\n\r\n\'). We own the data, and
471  // we're about to discard it, so this is ok...
472  *(end + 2) = 0;
473 
474  GST_DEBUG ("Reading headers from '%s'", data);
475  ReadHeaders ((gchar *)data);
476 
477  len = end - data + 4;
478  gst_adapter_flush (mAdapter, len);
479 
480  return gst_adapter_take_buffer (mAdapter, gst_adapter_available (mAdapter));
481 }
482 
483 /* Selected very arbitrarily */
484 #define MAX_INTERNAL_BUFFER (8*1024)
485 
486 /* Called when new data is available. All the data MUST be read.
487  *
488  * We read into our internal async queue, and then suspend the request
489  * for a little while if the queue is now 'full'. This avoids infinite
490  * memory use, which is generally considered good.
491  */
492 NS_IMETHODIMP
493 StreamListener::OnDataAvailable(nsIRequest *req, nsISupports *ctxt,
494  nsIInputStream *stream,
495  PRUint32 offset, PRUint32 count)
496 {
497  GST_DEBUG_OBJECT (mSrc, "%p::OnDataAvailable called: [count=%u, offset=%u]",
498  this, count, offset);
499 
500  nsresult rv;
501  PRUint32 bytesRead=0;
502  int len;
503 
504  GstBuffer *buf = gst_buffer_new_and_alloc (count);
505 
506  rv = stream->Read((char *)GST_BUFFER_DATA (buf), count, &bytesRead);
507  if (NS_FAILED(rv)) {
508  GST_DEBUG_OBJECT (mSrc, "stream->Read failed with rv=%x", rv);
509  return rv;
510  }
511 
512  GST_BUFFER_SIZE (buf) = bytesRead;
513 
514  if (mSrc->is_shoutcast_server && !mSrc->shoutcast_headers_read) {
515  GST_DEBUG_OBJECT (mSrc, "Scanning for headers");
516  // Scan the buffer for the end of the headers. Returns a new
517  // buffer if we have any non-header data.
518  buf = ScanForHeaders (buf);
519 
520  if (!buf) {
521  return NS_OK;
522  }
523  else {
524  mSrc->shoutcast_headers_read = TRUE;
525  }
526  }
527 
528  len = GST_BUFFER_SIZE (buf);
529 
530  if (mSrc->icy_caps)
531  gst_buffer_set_caps (buf, mSrc->icy_caps);
532 
533  g_mutex_lock (mSrc->queue_lock);
534 
535  mSrc->queue_size += len;
536  g_queue_push_tail (mSrc->queue, buf);
537  GST_DEBUG_OBJECT (mSrc, "Pushed %d byte buffer onto queue (now %d bytes)",
538  len, mSrc->queue_size);
539 
540  g_cond_signal (mSrc->queue_cond);
541 
542  /* If we're reading too quickly, we'll suspend after this.
543  * Suspend if:
544  * - we've gone over our buffer size
545  * - reading this much _again_ will put us over the buffer size, and we have
546  * at least half a buffer already.
547  *
548  * We're required to always read all the data.
549  */
550  if ((mSrc->queue_size > MAX_INTERNAL_BUFFER) ||
551  (mSrc->queue_size + len > MAX_INTERNAL_BUFFER &&
552  mSrc->queue_size > MAX_INTERNAL_BUFFER/2))
553  {
554  if (mSrc->suspended) {
555  GST_WARNING_OBJECT (mSrc, "Trying to suspend while already suspended, "
556  "unexpected!");
557  goto done;
558  }
559 
560  /* Then we should suspend the connection until we need more data.
561  * If our channel doesn't support nsIRequest, we can't do this, but
562  * that's not an error. */
563  nsCOMPtr<nsIRequest> request(do_QueryInterface(mSrc->channel));
564  if (request) {
565  GST_DEBUG_OBJECT (mSrc, "Suspending request, reading too fast!");
566  request->Suspend();
567  mSrc->suspended = TRUE;
568  }
569  }
570 
571 done:
572  g_mutex_unlock (mSrc->queue_lock);
573 
574  return NS_OK;
575 }
576 
577 static void
578 _urihandler_init (GType type)
579 {
580  static const GInterfaceInfo urihandler_info = {
582  NULL,
583  NULL
584  };
585 
586  g_type_add_interface_static (type, GST_TYPE_URI_HANDLER, &urihandler_info);
587 }
588 
589 GST_BOILERPLATE_FULL (GstMozillaSrc, gst_mozilla_src, GstPushSrc,
590  GST_TYPE_PUSH_SRC, _urihandler_init);
591 
592 static void
593 gst_mozilla_src_base_init (gpointer g_class)
594 {
595  GstElementClass *element_class = GST_ELEMENT_CLASS (g_class);
596 
597  gst_element_class_add_pad_template (element_class,
598  gst_static_pad_template_get (&srctemplate));
599 
600  gst_element_class_set_details (element_class, &gst_mozilla_src_details);
601 }
602 
603 static void
605 {
606  GObjectClass *gobject_class;
607  GstBaseSrcClass *gstbasesrc_class;
608  GstPushSrcClass *gstpushsrc_class;
609 
610  gobject_class = (GObjectClass *) klass;
611  gstbasesrc_class = (GstBaseSrcClass *) klass;
612  gstpushsrc_class = (GstPushSrcClass *) klass;
613 
614  gobject_class->set_property = gst_mozilla_src_set_property;
615  gobject_class->get_property = gst_mozilla_src_get_property;
616  gobject_class->finalize = gst_mozilla_src_finalize;
617 
618  g_object_class_install_property
619  (gobject_class, PROP_LOCATION,
620  g_param_spec_string ("location", "Location",
621  "Location to read from", "", (GParamFlags)G_PARAM_READWRITE));
622 
623  g_object_class_install_property
624  (gobject_class, PROP_IRADIO_MODE,
625  g_param_spec_boolean ("iradio-mode", "iradio-mode",
626  "Enable reading of shoutcast/icecast metadata",
627  FALSE, (GParamFlags)G_PARAM_READWRITE));
628 
629  gstbasesrc_class->start = GST_DEBUG_FUNCPTR (gst_mozilla_src_start);
630  gstbasesrc_class->stop = GST_DEBUG_FUNCPTR (gst_mozilla_src_stop);
631  gstbasesrc_class->get_size = GST_DEBUG_FUNCPTR (gst_mozilla_src_get_size);
632  gstbasesrc_class->is_seekable =
633  GST_DEBUG_FUNCPTR (gst_mozilla_src_is_seekable);
634  gstbasesrc_class->do_seek = GST_DEBUG_FUNCPTR (gst_mozilla_src_do_seek);
635  gstbasesrc_class->unlock = GST_DEBUG_FUNCPTR (gst_mozilla_src_unlock);
636  gstbasesrc_class->unlock_stop =
637  GST_DEBUG_FUNCPTR (gst_mozilla_src_unlock_stop);
638 
639  gstpushsrc_class->create = GST_DEBUG_FUNCPTR (gst_mozilla_src_create);
640 
641  GST_DEBUG_CATEGORY_INIT (mozillasrc_debug, "mozillasrc", 0,
642  "Mozilla Source");
643 }
644 
645 static void unref_buffer (gpointer data, gpointer user_data)
646 {
647  gst_buffer_unref ((GstBuffer *)data);
648 }
649 
650 static void
652 {
653  GST_DEBUG_OBJECT (src, "Flushing input queue");
654 
655  g_mutex_lock (src->queue_lock);
656  g_queue_foreach (src->queue, unref_buffer, NULL);
657  /* g_queue_clear (src->queue); // glib 2.14 required */
658  g_queue_free (src->queue);
659  src->queue = g_queue_new ();
660  g_mutex_unlock (src->queue_lock);
661 }
662 
663 static void
665 {
666  if (src->location) {
667  g_free ((void *)src->location);
668  src->location = NULL;
669  }
670 
671  if (src->icy_caps) {
672  gst_caps_unref (src->icy_caps);
673  src->icy_caps = NULL;
674  }
675 
676  src->content_size = -1;
677  src->current_position = 0;
678  src->is_seekable = TRUE; /* Only set to false once we know */
679 
680  src->is_shoutcast_server = FALSE;
681  src->shoutcast_headers_read = FALSE;
682 }
683 
684 static void
686 {
687  gst_mozilla_src_clear (src);
688 
689  src->queue_lock = new GMutex;
690  src->queue_cond = new GCond;
691 
692  g_mutex_init(src->queue_lock);
693  g_cond_init(src->queue_cond);
694  src->queue = g_queue_new ();
695 
696  src->iradio_mode = FALSE;
697 }
698 
699 static void
700 gst_mozilla_src_finalize (GObject * gobject)
701 {
702  GstMozillaSrc *src = GST_MOZILLA_SRC (gobject);
703 
704  GST_DEBUG_OBJECT (gobject, "Finalizing mozillasrc");
705 
706  gst_mozilla_src_flush (src);
707  gst_mozilla_src_clear (src);
708 
709  g_mutex_clear(src->queue_lock);
710  g_cond_clear(src->queue_cond);
711  g_queue_free(src->queue);
712 
713  G_OBJECT_CLASS (parent_class)->finalize (gobject);
714 }
715 
716 static void
717 gst_mozilla_src_set_property (GObject * object, guint prop_id,
718  const GValue * value, GParamSpec * pspec)
719 {
720  GstMozillaSrc *src = GST_MOZILLA_SRC (object);
721 
722  switch (prop_id) {
723  case PROP_LOCATION:
724  {
725  src->location = g_value_dup_string (value);
726  break;
727  }
728  case PROP_IRADIO_MODE:
729  {
730  src->iradio_mode = g_value_get_boolean (value);
731  break;
732  }
733  default:
734  G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
735  break;
736  }
737 }
738 
739 static void
740 gst_mozilla_src_get_property (GObject * object, guint prop_id,
741  GValue * value, GParamSpec * pspec)
742 {
743  GstMozillaSrc *src = GST_MOZILLA_SRC (object);
744 
745  switch (prop_id) {
746  case PROP_LOCATION:
747  {
748  g_value_set_string (value, src->location);
749  break;
750  }
751  case PROP_IRADIO_MODE:
752  {
753  g_value_set_boolean (value, src->iradio_mode);
754  break;
755  }
756  default:
757  G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
758  break;
759  }
760 }
761 
762 static gboolean
763 gst_mozilla_src_unlock(GstBaseSrc * psrc)
764 {
765  GstMozillaSrc *src = GST_MOZILLA_SRC (psrc);
766 
767  src->flushing = TRUE;
768 
769  GST_DEBUG_OBJECT (src, "unlock");
770  g_mutex_lock (src->queue_lock);
771  g_cond_signal (src->queue_cond);
772  g_mutex_unlock (src->queue_lock);
773 
774  return TRUE;
775 }
776 
777 static gboolean
778 gst_mozilla_src_unlock_stop(GstBaseSrc * psrc)
779 {
780  GstMozillaSrc *src = GST_MOZILLA_SRC (psrc);
781 
782  src->flushing = FALSE;
783 
784  GST_DEBUG_OBJECT (src, "unlock_stop");
785  g_mutex_lock (src->queue_lock);
786  g_cond_signal (src->queue_cond);
787  g_mutex_unlock (src->queue_lock);
788 
789  return TRUE;
790 }
791 
792 static GstFlowReturn
793 gst_mozilla_src_create (GstPushSrc * psrc, GstBuffer ** outbuf)
794 {
795  GstMozillaSrc *src;
796  GstBaseSrc *basesrc;
797  GstFlowReturn ret;
798 
799  src = GST_MOZILLA_SRC (psrc);
800  basesrc = GST_BASE_SRC_CAST (psrc);
801 
802  if (G_UNLIKELY (src->eos)) {
803  GST_DEBUG_OBJECT (src, "Create called at EOS");
804  return GST_FLOW_UNEXPECTED;
805  }
806 
807  if (G_UNLIKELY (src->flushing)) {
808  GST_DEBUG_OBJECT (src, "Create called while flushing");
809  return GST_FLOW_WRONG_STATE;
810  }
811 
812  /* Pop a buffer from our async queue, if possible. */
813  g_mutex_lock (src->queue_lock);
814 
815  GST_DEBUG_OBJECT (src, "Queue has %d bytes", src->queue_size);
816 
817  /* If the queue is empty, wait for it to be non-empty, or for us to be
818  * otherwise interrupted */
819  if (g_queue_is_empty (src->queue)) {
820  GST_DEBUG_OBJECT (src, "Queue is empty; waiting");
821 
822  /* Ask mozilla to resume reading the stream */
823  nsCOMPtr<nsIRunnable> resume_event = new ResumeEvent (src);
824  NS_DispatchToMainThread (resume_event);
825 
826  GST_DEBUG_OBJECT (src, "Starting wait");
827  g_cond_wait (src->queue_cond, src->queue_lock);
828  GST_DEBUG_OBJECT (src, "Wait done, we should have a buffer now");
829 
830  /* If it's still empty, we were interrupted by something, so return a
831  * failure */
832  if (g_queue_is_empty (src->queue)) {
833  GST_DEBUG_OBJECT (src, "Still no buffer; bailing");
834  ret = GST_FLOW_UNEXPECTED;
835  goto done;
836  }
837  }
838 
839  /* And otherwise, we're fine: pop the next buffer and return */
840  *outbuf = (GstBuffer *)g_queue_pop_head (src->queue);
841  src->queue_size -= GST_BUFFER_SIZE (*outbuf);
842 
843  GST_BUFFER_OFFSET (*outbuf) = src->current_position;
844  src->current_position += GST_BUFFER_SIZE (*outbuf);
845 
846  GST_DEBUG_OBJECT (src, "Popped %d byte buffer from queue",
847  GST_BUFFER_SIZE (*outbuf));
848  ret = GST_FLOW_OK;
849 
850 done:
851  g_mutex_unlock (src->queue_lock);
852 
853  return ret;
854 }
855 
856 static void
858 {
859  nsCOMPtr<nsIRequest> request(do_QueryInterface(src->channel));
860  if (request) {
861  if (src->suspended) {
862  /* Cancel doesn't work while suspended */
863  GST_DEBUG_OBJECT (src, "Resuming request for cancel");
864  request->Resume();
865  src->suspended = FALSE;
866  }
867 
868  // Set this so we can distinguish between cancelling a request,
869  // and reaching EOS.
870  src->is_cancelled = TRUE;
871 
872  GST_DEBUG_OBJECT (src, "Cancelling request");
873  request->Cancel(NS_BINDING_ABORTED);
874  }
875 
876  src->channel = 0;
877 }
878 
879 static gboolean
881 {
882  nsresult rv;
883  nsCOMPtr<nsIStreamListener> listener = new StreamListener(src);
884 
885  rv = NS_NewChannel(getter_AddRefs(src->channel), src->uri,
886  nsnull, nsnull, nsnull,
887  nsIRequest::LOAD_BYPASS_CACHE | nsIRequest::INHIBIT_CACHING);
888  if (NS_FAILED (rv)) {
889  GST_WARNING_OBJECT (src, "Failed to create channel for %s", src->location);
890  return FALSE;
891  }
892 
893  nsCOMPtr<nsIInterfaceRequestor> requestor = do_QueryInterface (listener);
894  src->channel->SetNotificationCallbacks(requestor);
895 
896  if (segment && segment->format == GST_FORMAT_BYTES && segment->start > 0) {
897  nsCOMPtr<nsIResumableChannel> resumable(do_QueryInterface(src->channel));
898  if (resumable) {
899  GST_DEBUG_OBJECT (src, "Trying to resume at %d bytes", segment->start);
900 
901  rv = resumable->ResumeAt(segment->start, EmptyCString());
902  // If we failed, just log it and continue; it's not critical.
903  if (NS_FAILED (rv))
904  GST_WARNING_OBJECT (src,
905  "Failed to resume channel at non-zero offsets");
906  else
907  src->current_position = segment->start;
908  }
909  }
910 
911  nsCOMPtr<nsIHttpChannel> httpchannel(do_QueryInterface(src->channel));
912 
913  if (httpchannel) {
914  NS_NAMED_LITERAL_CSTRING (useragent, "User-Agent");
915  NS_NAMED_LITERAL_CSTRING (mozilla, "Mozilla");
916  NS_NAMED_LITERAL_CSTRING (notmozilla, "NotMoz");
917  nsCAutoString agent;
918 
919  // Set up to receive shoutcast-style metadata, if in that mode.
920  if (src->iradio_mode) {
921  rv = httpchannel->SetRequestHeader(
922  NS_LITERAL_CSTRING("icy-metadata"),
923  NS_LITERAL_CSTRING("1"),
924  PR_FALSE);
925  if (NS_FAILED (rv))
926  GST_WARNING_OBJECT (src,
927  "Failed to set icy-metadata header on channel");
928  }
929 
930  // Some servers (notably shoutcast) serve html to web browsers, and the
931  // stream to everything else. So, use a different user-agent so as not to
932  // look like a web browser - mangle the existing one to not mention
933  // Mozilla (we change 'Mozilla' to 'NotMoz'.
934  rv = httpchannel->GetRequestHeader (useragent, agent);
935 
936  if (NS_SUCCEEDED (rv)) {
937  int offset;
938  GST_DEBUG_OBJECT (src, "Default User-Agent is '%s'",
939  agent.BeginReading());
940 
941  offset = agent.Find(mozilla);
942  if (offset >= 0) {
943  agent.Replace(offset, mozilla.Length(), notmozilla);
944  }
945 
946  GST_DEBUG_OBJECT (src, "Actual User-Agent is '%s'",
947  agent.BeginReading());
948 
949  rv = httpchannel->SetRequestHeader(useragent, agent, PR_FALSE);
950  if (NS_FAILED (rv))
951  GST_WARNING_OBJECT (src, "Failed to set user agent on channel");
952  }
953  }
954 
955  rv = src->channel->AsyncOpen(listener, nsnull);
956  if (NS_FAILED (rv)) {
957  GST_WARNING_OBJECT (src, "Failed to open channel for %s", src->location);
958  return FALSE;
959  }
960 
961  return TRUE;
962 }
963 
964 static gboolean
965 gst_mozilla_src_start (GstBaseSrc * bsrc)
966 {
967  GstMozillaSrc *src = GST_MOZILLA_SRC (bsrc);
968  nsresult rv;
969 
970  if (!src->location) {
971  GST_WARNING_OBJECT (src, "No location set");
972  return FALSE;
973  }
974 
975  rv = NS_NewURI(getter_AddRefs(src->uri), src->location);
976  if (NS_FAILED (rv)) {
977  GST_WARNING_OBJECT (src, "Failed to create URI from %s", src->location);
978  return FALSE;
979  }
980 
981  /* Send off a request! */
982  if (gst_mozilla_src_create_request (src, NULL)) {
983  GST_DEBUG_OBJECT (src, "Started request");
984  return TRUE;
985  }
986  else {
987  GST_ELEMENT_ERROR (src, LIBRARY, INIT,
988  (NULL), ("Failed to initialise mozilla to fetch uri %s",
989  src->location));
990  return FALSE;
991  }
992 }
993 
994 static gboolean
995 gst_mozilla_src_stop (GstBaseSrc * bsrc)
996 {
997  GstMozillaSrc *src = GST_MOZILLA_SRC (bsrc);
998 
999  GST_INFO_OBJECT (src, "Stop(): shutting down connection");
1000 
1001  if (src->channel) {
1002  nsCOMPtr<nsIRequest> request(do_QueryInterface(src->channel));
1003  if (request) {
1004  GST_DEBUG_OBJECT (src, "Cancelling request");
1005  request->Cancel(NS_BINDING_ABORTED);
1006  }
1007 
1008  // Delete the underlying nsIChannel.
1009  src->channel = 0;
1010  src->uri = 0;
1011  }
1012 
1013  return TRUE;
1014 }
1015 
1016 static gboolean
1017 gst_mozilla_src_get_size (GstBaseSrc * bsrc, guint64 * size)
1018 {
1019  GstMozillaSrc *src;
1020 
1021  src = GST_MOZILLA_SRC (bsrc);
1022 
1023  if (src->content_size == -1)
1024  return FALSE;
1025 
1026  *size = src->content_size;
1027 
1028  return TRUE;
1029 }
1030 
1031 
1032 static gboolean
1033 gst_mozilla_src_is_seekable (GstBaseSrc * bsrc)
1034 {
1035  /* We always claim seekability, and then fail it when a seek is attempted if
1036  * we can't support it - we don't know at this point if it's actually
1037  * seekable or not (we only know that once we've received headers) */
1038  return TRUE;
1039 }
1040 
1041 static gboolean
1042 gst_mozilla_src_do_seek (GstBaseSrc * bsrc, GstSegment * segment)
1043 {
1044  GstMozillaSrc *src = GST_MOZILLA_SRC (bsrc);
1045 
1046  if (segment->start == src->current_position) {
1047  /* We're being asked to seek to the where we already are (this includes
1048  * the initial seek to zero); so just return success */
1049  return TRUE;
1050  }
1051 
1052  if (!src->is_seekable) {
1053  GST_INFO_OBJECT (src, "is_seekable FALSE; failing seek immediately");
1054  return FALSE;
1055  }
1056 
1057  /* Cancel current request; create a new one */
1059 
1060  if (gst_mozilla_src_create_request (src, segment)) {
1061  GST_INFO_OBJECT (src, "New request for seek initiated");
1062  return TRUE;
1063  }
1064  else {
1065  GST_WARNING_OBJECT (src, "Creating new request for seek failed");
1066  return FALSE;
1067  }
1068 }
1069 
1070 /* GstURIHandler Interface */
1071 static GstURIType
1073 {
1074  return GST_URI_SRC;
1075 }
1076 
1077 static gchar **_supported_protocols = NULL;
1078 
1079 static gchar **
1081 {
1082  /* Return the cache if we've created it */
1083  if (_supported_protocols != NULL)
1084  return _supported_protocols;
1085 
1086  int numprotocols = 0;
1087  nsresult rv;
1088  nsCOMPtr<nsIComponentRegistrar> registrar;
1089  rv = NS_GetComponentRegistrar(getter_AddRefs(registrar));
1090  NS_ENSURE_SUCCESS(rv, NULL);
1091 
1092  nsCOMPtr<nsISimpleEnumerator> simpleEnumerator;
1093  rv = registrar->EnumerateContractIDs(getter_AddRefs(simpleEnumerator));
1094  NS_ENSURE_SUCCESS(rv, NULL);
1095 
1096  // Enumerate through the contractIDs and look for a specific prefix
1097  nsCOMPtr<nsISupports> element;
1098  PRBool more = PR_FALSE;
1099  while(NS_SUCCEEDED(simpleEnumerator->HasMoreElements(&more)) && more) {
1100 
1101  rv = simpleEnumerator->GetNext(getter_AddRefs(element));
1102  NS_ENSURE_SUCCESS(rv, NULL);
1103 
1104  nsCOMPtr<nsISupportsCString> contractString =
1105  do_QueryInterface(element, &rv);
1106  if NS_FAILED(rv) {
1107  NS_WARNING("QueryInterface failed");
1108  continue;
1109  }
1110 
1111  nsCAutoString contractID;
1112  rv = contractString->GetData(contractID);
1113  if NS_FAILED(rv) {
1114  NS_WARNING("GetData failed");
1115  continue;
1116  }
1117 
1118  NS_NAMED_LITERAL_CSTRING(prefix, NS_NETWORK_PROTOCOL_CONTRACTID_PREFIX);
1119 
1120  if (!StringBeginsWith(contractID, prefix))
1121  continue;
1122 
1123  nsCAutoString scheme = contractID;
1124  scheme.Cut(0, prefix.Length());
1125 
1126  if (scheme.Equals(NS_LITERAL_CSTRING("file")))
1127  {
1128  // We don't want to handle normal files.
1129  GST_DEBUG ("Skipping file scheme");
1130  continue;
1131  }
1132 
1133  GST_DEBUG ("Adding scheme '%s'", scheme.BeginReading());
1134 
1135  // Now add it to our null-terminated array.
1136  numprotocols++;
1137  _supported_protocols = (gchar **)g_realloc (_supported_protocols,
1138  sizeof(gchar *) * (numprotocols + 1));
1139  _supported_protocols[numprotocols - 1] = g_strdup (scheme.BeginReading());
1140  _supported_protocols[numprotocols] = NULL;
1141  }
1142 
1143  return _supported_protocols;
1144 }
1145 
1146 static const gchar *
1148 {
1149  GstMozillaSrc *src = GST_MOZILLA_SRC (handler);
1150 
1151  return src->location;
1152 }
1153 
1154 static gboolean
1155 gst_mozilla_src_uri_set_uri (GstURIHandler * handler, const gchar * uri)
1156 {
1157  GstMozillaSrc *src = GST_MOZILLA_SRC (handler);
1158 
1159  GST_DEBUG_OBJECT (src, "URI set to '%s'", uri);
1160 
1161  src->location = g_strdup (uri);
1162  return TRUE;
1163 }
1164 
1165 static void
1166 gst_mozilla_src_uri_handler_init (gpointer g_iface, gpointer iface_data)
1167 {
1168  GstURIHandlerInterface *iface = (GstURIHandlerInterface *) g_iface;
1169 
1170  iface->get_type = gst_mozilla_src_uri_get_type;
1171  iface->get_protocols = gst_mozilla_src_uri_get_protocols;
1172  iface->get_uri = gst_mozilla_src_uri_get_uri;
1173  iface->set_uri = gst_mozilla_src_uri_set_uri;
1174 }
nsCOMPtr< nsIChannel > channel
Definition: mozillasrc.cpp:74
GST_DEBUG_CATEGORY_STATIC(mozillasrc_debug)
static gboolean gst_mozilla_src_stop(GstBaseSrc *bsrc)
Definition: mozillasrc.cpp:995
static const GstElementDetails gst_mozilla_src_details
Definition: mozillasrc.cpp:94
GMutex * queue_lock
Definition: mozillasrc.cpp:83
gboolean suspended
Definition: mozillasrc.cpp:75
static gboolean gst_mozilla_src_create_request(GstMozillaSrc *src, GstSegment *segment)
Definition: mozillasrc.cpp:880
static GstStaticPadTemplate srctemplate
Definition: mozillasrc.cpp:101
return NS_OK
var registrar
nsCOMPtr< nsIURI > uri
Definition: mozillasrc.cpp:71
gint64 current_position
Definition: mozillasrc.cpp:62
virtual ~StreamListener()
Definition: mozillasrc.cpp:201
gboolean is_seekable
Definition: mozillasrc.cpp:63
function succeeded(ch, cx, status, data)
GQueue * queue
Definition: mozillasrc.cpp:81
static gboolean gst_mozilla_src_start(GstBaseSrc *bsrc)
Definition: mozillasrc.cpp:965
sbDeviceFirmwareAutoCheckForUpdate prototype contractID
static const gchar * gst_mozilla_src_uri_get_uri(GstURIHandler *handler)
gboolean flushing
Definition: mozillasrc.cpp:55
sbOSDControlService prototype QueryInterface
var header
Definition: FeedWriter.js:953
static void gst_mozilla_src_cancel_request(GstMozillaSrc *src)
Definition: mozillasrc.cpp:857
gint64 content_size
Definition: mozillasrc.cpp:52
GCond * queue_cond
Definition: mozillasrc.cpp:84
NS_DECL_ISUPPORTS NS_DECL_NSIRUNNABLE ResumeEvent(GstMozillaSrc *src)
Definition: mozillasrc.cpp:140
NS_DECL_ISUPPORTS NS_DECL_NSIREQUESTOBSERVER NS_DECL_NSISTREAMLISTENER NS_DECL_NSIHTTPHEADERVISITOR NS_DECL_NSIINTERFACEREQUESTOR NS_DECL_NSIHTTPEVENTSINK StreamListener(GstMozillaSrc *aSrc)
Definition: mozillasrc.cpp:195
static void gst_mozilla_src_clear(GstMozillaSrc *src)
Definition: mozillasrc.cpp:664
GstPushSrc element
Definition: mozillasrc.cpp:50
static gchar ** gst_mozilla_src_uri_get_protocols(void)
gboolean iradio_mode
Definition: mozillasrc.cpp:57
PRUint32 & offset
gboolean is_cancelled
Definition: mozillasrc.cpp:65
var count
Definition: test_bug7406.js:32
const gchar * location
Definition: mozillasrc.cpp:69
GST_BOILERPLATE_FULL(GstMozillaSrc, gst_mozilla_src, GstPushSrc, GST_TYPE_PUSH_SRC, _urihandler_init)
static void gst_mozilla_src_get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec)
Definition: mozillasrc.cpp:740
static void gst_mozilla_src_class_init(GstMozillaSrcClass *klass)
Definition: mozillasrc.cpp:604
static void gst_mozilla_src_flush(GstMozillaSrc *src)
Definition: mozillasrc.cpp:651
const nsIChannel
static gboolean gst_mozilla_src_get_size(GstBaseSrc *bsrc, guint64 *size)
unique done
static GstFlowReturn gst_mozilla_src_create(GstPushSrc *psrc, GstBuffer **outbuf)
Definition: mozillasrc.cpp:793
GstPushSrcClass parent_class
Definition: mozillasrc.cpp:88
gboolean eos
Definition: mozillasrc.cpp:54
return ret
NS_IMPL_THREADSAFE_ISUPPORTS5(StreamListener, nsIRequestObserver, nsIStreamListener, nsIHttpHeaderVisitor, nsIInterfaceRequestor, nsIHttpEventSink) NS_IMETHODIMP StreamListener
Definition: mozillasrc.cpp:207
static void gst_mozilla_src_base_init(gpointer g_class)
Definition: mozillasrc.cpp:593
static gboolean gst_mozilla_src_do_seek(GstBaseSrc *bsrc, GstSegment *segment)
#define GST_MOZILLA_SRC(obj)
Definition: mozillaplugin.h:33
gboolean shoutcast_headers_read
Definition: mozillasrc.cpp:60
static GstURIType gst_mozilla_src_uri_get_type(void)
var uri
Definition: FeedWriter.js:1135
countRef value
Definition: FeedWriter.js:1423
static gboolean gst_mozilla_src_is_seekable(GstBaseSrc *bsrc)
static void gst_mozilla_src_uri_handler_init(gpointer g_iface, gpointer iface_data)
static void gst_mozilla_src_finalize(GObject *gobject)
Definition: mozillasrc.cpp:700
NS_IMPL_THREADSAFE_ISUPPORTS1(ResumeEvent, nsIRunnable) NS_IMETHODIMP ResumeEvent
Definition: mozillasrc.cpp:153
GstCaps * icy_caps
Definition: mozillasrc.cpp:58
const NS_BINDING_ABORTED
static gchar ** _supported_protocols
static gboolean gst_mozilla_src_uri_set_uri(GstURIHandler *handler, const gchar *uri)
static void gst_mozilla_src_set_property(GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec)
Definition: mozillasrc.cpp:717
const nsIInterfaceRequestor
observe data
Definition: FeedWriter.js:1329
#define MAX_INTERNAL_BUFFER
Definition: mozillasrc.cpp:484
static void _urihandler_init(GType type)
Definition: mozillasrc.cpp:578
static void unref_buffer(gpointer data, gpointer user_data)
Definition: mozillasrc.cpp:645
GstMessage gpointer data sbGStreamerMessageHandler * handler
gboolean is_shoutcast_server
Definition: mozillasrc.cpp:59
static void gst_mozilla_src_init(GstMozillaSrc *src, GstMozillaSrcClass *g_class)
Definition: mozillasrc.cpp:685
static gboolean gst_mozilla_src_unlock_stop(GstBaseSrc *psrc)
Definition: mozillasrc.cpp:778
static gboolean gst_mozilla_src_unlock(GstBaseSrc *psrc)
Definition: mozillasrc.cpp:763