sbGStreamerMetadataHandler.cpp
Go to the documentation of this file.
1 // vim: set sw=2 :
2 /*
3 //
4 // BEGIN SONGBIRD GPL
5 //
6 // This file is part of the Songbird web player.
7 //
8 // Copyright(c) 2005-2008 POTI, Inc.
9 // http://songbirdnest.com
10 //
11 // This file may be licensed under the terms of of the
12 // GNU General Public License Version 2 (the "GPL").
13 //
14 // Software distributed under the License is distributed
15 // on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either
16 // express or implied. See the GPL for the specific language
17 // governing rights and limitations.
18 //
19 // You should have received a copy of the GPL along with this
20 // program. If not, go to http://www.gnu.org/licenses/gpl.html
21 // or write to the Free Software Foundation, Inc.,
22 // 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
23 //
24 // END SONGBIRD GPL
25 //
26 */
27 
29 
32 
33 #include <sbIGStreamerService.h>
34 #include <sbIMediacoreCapabilities.h>
35 
36 #include <sbMemoryUtils.h>
37 #include <sbStandardProperties.h>
38 #include <sbPropertiesCID.h>
39 
40 #include <nsComponentManagerUtils.h>
41 #include <nsProxyRelease.h>
42 #include <nsServiceManagerUtils.h>
43 #include <nsThreadUtils.h>
44 #include <nsIStringEnumerator.h>
45 #include <nsIURI.h>
46 #include <nsUnicharUtils.h>
47 
48 #include <prlog.h>
49 #include <prtime.h>
50 
51 #include <gst/pbutils/missing-plugins.h>
52 
53 #define METADATA_TIMEOUT (30 * PR_MSEC_PER_SEC)
54 
55 #ifdef PR_LOGGING
56 static PRLogModuleInfo* gGSTMetadataLog = nsnull;
57 #define TRACE(args) \
58  PR_BEGIN_MACRO \
59  if (!gGSTMetadataLog) \
60  gGSTMetadataLog = PR_NewLogModule("gstmetadata"); \
61  PR_LOG(gGSTMetadataLog, PR_LOG_DEBUG, args); \
62  PR_END_MACRO
63 #define LOG(args) \
64  PR_BEGIN_MACRO \
65  if (!gGSTMetadataLog) \
66  gGSTMetadataLog = PR_NewLogModule("gstmetadata"); \
67  PR_LOG(gGSTMetadataLog, PR_LOG_WARN, args); \
68  PR_END_MACRO
69 #else
70 #define TRACE(args) /* nothing */
71 #define LOG(args) /* nothing */
72 #endif
73 
74 SB_AUTO_CLASS(sbGstElement,
75  GstElement*,
76  !!mValue,
77  gst_object_unref(mValue),
78  mValue = NULL);
79 SB_AUTO_CLASS(sbGstBus,
80  GstBus*,
81  !!mValue,
82  gst_object_unref(mValue),
83  mValue = NULL);
84 SB_AUTO_CLASS(sbGstPad,
85  GstPad*,
86  !!mValue,
87  gst_object_unref(mValue),
88  mValue = NULL);
89 
93 
95  : mLock(nsnull),
96  mPipeline(nsnull),
97  mTags(nsnull),
98  mHasAudio(PR_FALSE),
99  mHasVideo(PR_FALSE),
100  mCompleted(PR_FALSE)
101 {
102  /* member initializers and constructor code */
103 }
104 
105 sbGStreamerMetadataHandler::~sbGStreamerMetadataHandler()
106 {
107  Close();
108  if (mLock) {
109  nsAutoLock::DestroyLock(mLock);
110  mLock = nsnull;
111  }
112 }
113 
114 nsresult
116 {
117  TRACE((__FUNCTION__));
118  mLock = nsAutoLock::NewLock("sbGStreamerMetadataHandler::mLock");
119  NS_ENSURE_TRUE(mLock, NS_ERROR_OUT_OF_MEMORY);
120 
121  // initialize GStreamer
122  nsresult rv;
123  nsCOMPtr<nsISupports> gstService =
124  do_GetService(SBGSTREAMERSERVICE_CONTRACTID , &rv);
125  NS_ENSURE_SUCCESS(rv, rv);
126 
127  return NS_OK;
128 }
129 
130 /* readonly attribute ACString contractID */
131 NS_IMETHODIMP
132 sbGStreamerMetadataHandler::GetContractID(nsACString &aContractID)
133 {
134  aContractID.AssignLiteral(SB_GSTREAMER_METADATA_HANDLER_CONTRACTID);
135  return NS_OK;
136 }
137 
138 /* attribute sbIMutablePropertyArray props; */
139 NS_IMETHODIMP
140 sbGStreamerMetadataHandler::GetProps(sbIMutablePropertyArray * *aProps)
141 {
142  TRACE((__FUNCTION__));
143  NS_ENSURE_ARG_POINTER(aProps);
144  *aProps = mProperties;
145  NS_ENSURE_STATE(*aProps);
146 
147  NS_ADDREF(*aProps);
148  return NS_OK;
149 }
150 NS_IMETHODIMP
151 sbGStreamerMetadataHandler::SetProps(sbIMutablePropertyArray * aProps)
152 {
153  TRACE((__FUNCTION__));
154  // we don't support writing, go away
155  return NS_ERROR_NOT_IMPLEMENTED;
156 }
157 
158 /* readonly attribute PRBool completed; */
159 NS_IMETHODIMP
160 sbGStreamerMetadataHandler::GetCompleted(PRBool *aCompleted)
161 {
162  TRACE((__FUNCTION__));
163  NS_ENSURE_ARG_POINTER(aCompleted);
164  *aCompleted = mCompleted;
165  TRACE(("%s: completed? %s", __FUNCTION__, *aCompleted ? "true" : "false"));
166  return NS_OK;
167 }
168 
169 /* readonly attribute PRBool requiresMainThread; */
170 NS_IMETHODIMP
171 sbGStreamerMetadataHandler::GetRequiresMainThread(PRBool *aRequiresMainThread)
172 {
173  TRACE((__FUNCTION__));
174  NS_ENSURE_ARG_POINTER(aRequiresMainThread);
175  // This handler MUST run on the main thread, since it can kick off
176  // http channels and other not-so-threadsafe async tasks.
177  *aRequiresMainThread = PR_TRUE;
178  return NS_OK;
179 }
180 
181 /* attribute nsIChannel channel; */
182 NS_IMETHODIMP
183 sbGStreamerMetadataHandler::GetChannel(nsIChannel * *aChannel)
184 {
185  TRACE((__FUNCTION__));
186  NS_ENSURE_ARG_POINTER(aChannel);
187  NS_IF_ADDREF(*aChannel = mChannel);
188  return NS_OK;
189 }
190 
191 NS_IMETHODIMP
192 sbGStreamerMetadataHandler::SetChannel(nsIChannel * aChannel)
193 {
194  TRACE((__FUNCTION__));
195  nsresult rv;
196  rv = Close();
197  NS_ENSURE_SUCCESS(rv, rv);
198 
199  nsAutoLock lock(mLock);
200  mChannel = aChannel;
201  if (mChannel) {
202  nsCOMPtr<nsIURI> uri;
203  rv = mChannel->GetURI(getter_AddRefs(uri));
204  NS_ENSURE_SUCCESS(rv, rv);
205 
206  rv = uri->GetSpec(mSpec);
207  NS_ENSURE_SUCCESS(rv, rv);
208  } else {
209  mSpec.SetIsVoid(PR_TRUE);
210  }
211  return NS_OK;
212 }
213 
214 static PRBool
215 HasExtensionInEnumerator(const nsAString& aHaystack, nsIStringEnumerator *aEnum)
216 {
217  TRACE((__FUNCTION__));
218  nsresult rv;
219 
220  while (PR_TRUE) {
221  PRBool hasMore;
222  rv = aEnum->HasMore(&hasMore);
223  NS_ENSURE_SUCCESS(rv, PR_FALSE);
224  if (!hasMore) {
225  break;
226  }
227 
228  nsString string;
229  rv = aEnum->GetNext(string);
230  NS_ENSURE_SUCCESS(rv, PR_FALSE);
231 
232  string.Insert('.', 0);
233  if (aHaystack.Find(string, CaseInsensitiveCompare) >= 0) {
234  return PR_TRUE;
235  }
236  }
237 
238  return PR_FALSE;
239 }
240 
241 /* PRInt32 vote (in AString aUrl); */
242 NS_IMETHODIMP
243 sbGStreamerMetadataHandler::Vote(const nsAString & aUrl, PRInt32 *_retval)
244 {
245  TRACE((__FUNCTION__));
246  NS_ENSURE_ARG_POINTER(_retval);
247  *_retval = -1;
248 
249  nsCString url = NS_ConvertUTF16toUTF8(aUrl); // we deal with 8-bit strings
250 
251  // check for a working protocol (scheme)
252  PRInt32 colonPos = url.Find(":");
253  NS_ENSURE_TRUE(colonPos >= 0, NS_OK);
254  nsCString proto = PromiseFlatCString(StringHead(url, colonPos));
255  gboolean protoSupported =
256  gst_uri_protocol_is_supported(GST_URI_SRC, proto.get());
257  if (!protoSupported) {
258  TRACE(("%s: protocol %s not supported", __FUNCTION__, proto.get()));
259  return NS_OK;
260  }
261 
262  // at this point, we _possibly_ support it, but not as sure as we like
263  // let's guess at file extensions! Oh wait, no, we have to guess substrings!
264  *_retval = 1;
265  nsresult rv;
266 
267  { /* for scope */
268  nsAutoLock lock(mLock);
269  if (!mFactory) {
271  NS_ENSURE_SUCCESS(rv, rv);
272  }
273  }
274 
275  nsCOMPtr<sbIMediacoreCapabilities> caps;
276  rv = mFactory->GetCapabilities(getter_AddRefs(caps));
277  NS_ENSURE_SUCCESS(rv, rv);
278 
279  nsCOMPtr<nsIStringEnumerator> strings;
280  PRBool found = PR_FALSE;
281  rv = caps->GetAudioExtensions(getter_AddRefs(strings));
282  if (NS_SUCCEEDED(rv) && strings) {
283  found |= HasExtensionInEnumerator(aUrl, strings);
284  }
285  rv = caps->GetVideoExtensions(getter_AddRefs(strings));
286  if (NS_SUCCEEDED(rv) && strings) {
287  found |= HasExtensionInEnumerator(aUrl, strings);
288  }
289  rv = caps->GetImageExtensions(getter_AddRefs(strings));
290  if (NS_SUCCEEDED(rv) && strings) {
291  found |= HasExtensionInEnumerator(aUrl, strings);
292  }
293  if (!found) {
294  TRACE(("%s: failed to match any supported extensions", __FUNCTION__));
295  return NS_OK;
296  }
297 
298  // okay, we probably support this. But quite possibly not as well as, say,
299  // TagLib (which at this point does magical charset detection).
300  *_retval = 80;
301  return NS_OK;
302 }
303 
304 /* PRInt32 read (); */
305 NS_IMETHODIMP
306 sbGStreamerMetadataHandler::Read(PRInt32 *_retval)
307 {
308  TRACE((__FUNCTION__));
309  NS_ENSURE_ARG_POINTER(_retval);
310 
311  nsresult rv;
312 
313  rv = Close();
314  NS_ENSURE_SUCCESS(rv, rv);
315 
316  sbGstElement decodeBin, pipeline;
317  sbGstBus bus;
318  GstStateChangeReturn stateReturn;
319 
320  { /* scope */
321  nsAutoLock lock(mLock);
322  mProperties = nsnull;
323 
324  mTimer = do_CreateInstance(NS_TIMER_CONTRACTID, &rv);
325  NS_ENSURE_SUCCESS(rv, rv);
326 
327  nsCOMPtr<nsITimerCallback> timerCallback =
328  do_QueryInterface(NS_ISUPPORTS_CAST(nsITimerCallback*, this));
329  rv = mTimer->InitWithCallback(timerCallback,
331  nsITimer::TYPE_ONE_SHOT);
332  NS_ENSURE_SUCCESS(rv, rv);
333 
334 
335  rv = NS_ERROR_OUT_OF_MEMORY;
336  NS_ASSERTION(!mPipeline, "pipeline already exists!");
337  if (mPipeline) {
338  // should never land here
339  gst_object_unref(mPipeline);
340  mPipeline = NULL;
341  }
342  pipeline = gst_pipeline_new("metadata-pipeline");
344  }
345  NS_ENSURE_TRUE(pipeline, NS_ERROR_OUT_OF_MEMORY);
346 
347  decodeBin = gst_element_factory_make("uridecodebin", "metadata-decodebin");
348  NS_ENSURE_TRUE(decodeBin, NS_ERROR_OUT_OF_MEMORY);
349  gst_bin_add(GST_BIN_CAST(pipeline.get()), decodeBin.get());
350  gst_object_ref(decodeBin.get()); // the ref was taken by the bin
351 
352  bus = gst_pipeline_get_bus(GST_PIPELINE_CAST(pipeline.get()));
353  NS_ENSURE_TRUE(bus, NS_ERROR_FAILURE);
354 
355  g_signal_connect(decodeBin.get(), "pad-added", G_CALLBACK(on_pad_added), this);
356 
357  // We want to receive state-changed messages when shutting down, so we
358  // need to turn off bus auto-flushing
359  g_object_set(pipeline.get(), "auto-flush-bus", FALSE, NULL);
360 
361  // Handle GStreamer messages synchronously, either directly or
362  // dispatching to the main thread.
363  gst_bus_set_sync_handler(bus.get(), SyncToAsyncDispatcher,
364  static_cast<sbGStreamerMessageHandler*>(this));
365 
366  TRACE(("%s: Setting URI to [%s]", __FUNCTION__, mSpec.get()));
367  g_object_set(G_OBJECT(decodeBin.get()), "uri", mSpec.get(), NULL);
368 
369  stateReturn = gst_element_set_state(pipeline.get(), GST_STATE_PAUSED);
370  NS_ENSURE_TRUE(stateReturn == GST_STATE_CHANGE_SUCCESS ||
371  stateReturn == GST_STATE_CHANGE_ASYNC,
372  NS_ERROR_FAILURE);
373 
374  *_retval = -1; // asynchronous
375  { /* scope */
376  nsAutoLock lock(mLock);
377  mPipeline = pipeline.forget();
378  }
379 
380  return NS_OK;
381 }
382 
383 /* PRInt32 write (); */
384 NS_IMETHODIMP
385 sbGStreamerMetadataHandler::Write(PRInt32 *_retval)
386 {
387  TRACE((__FUNCTION__));
388  return NS_ERROR_NOT_IMPLEMENTED;
389 }
390 
391 /* void getImageData (in PRInt32 aType, out AUTF8String aMimeType,
392  out unsigned long aDataLen,
393  [array, size_is (aDataLen), retval] out octet aData); */
394 NS_IMETHODIMP
395 sbGStreamerMetadataHandler::GetImageData(PRInt32 aType, nsACString & aMimeType,
396  PRUint32 *aDataLen, PRUint8 **aData)
397 {
398  TRACE((__FUNCTION__));
399  return NS_ERROR_NOT_IMPLEMENTED;
400 }
401 
402 /* void setImageData (in PRInt32 aType, in AString aUrl); */
403 NS_IMETHODIMP
404 sbGStreamerMetadataHandler::SetImageData(PRInt32 aType, const nsAString & aUrl)
405 {
406  TRACE((__FUNCTION__));
407  return NS_ERROR_NOT_IMPLEMENTED;
408 }
409 
410 /* void onChannelData (in nsISupports aChannel); */
411 NS_IMETHODIMP
412 sbGStreamerMetadataHandler::OnChannelData(nsISupports *aChannel)
413 {
414  TRACE((__FUNCTION__));
415  /* this is never used, don't bother implementing, ever */
416  return NS_ERROR_NOT_IMPLEMENTED;
417 }
418 
419 /* void close (); */
420 NS_IMETHODIMP
421 sbGStreamerMetadataHandler::Close()
422 {
423  TRACE((__FUNCTION__));
424  GstElement *pipeline = NULL;
425 
426  { /* scope */
427  nsAutoLock lock(mLock);
428  mCompleted = PR_FALSE;
429  pipeline = mPipeline;
430  if (pipeline) {
431  gst_object_ref(pipeline);
432  }
433  if (mTimer) {
434  nsresult rv = mTimer->Cancel();
435  if (NS_FAILED(rv)) {
436  NS_WARNING("Failed to cancel metadata timeout timer");
437  /* fail silently */
438  }
439  mTimer = nsnull;
440  }
441  }
442  if (pipeline) {
443  gst_element_set_state (pipeline, GST_STATE_NULL);
444  gst_object_unref (pipeline);
445  }
446  { /* scope */
447  nsAutoLock lock(mLock);
448  if (mPipeline) {
449  gst_object_unref(mPipeline);
450  }
451  mPipeline = NULL;
452 
453  if (mTags) {
454  NS_ASSERTION(GST_IS_TAG_LIST(mTags), "tags is not a tag list!?");
455  gst_tag_list_free (mTags);
456  mTags = nsnull;
457  }
458  }
459 
460  nsresult rv;
461  nsCOMPtr<nsIThread> mainThread;
462  rv = NS_GetMainThread(getter_AddRefs(mainThread));
463  NS_ENSURE_SUCCESS(rv, rv);
464 
465  nsCOMPtr<nsIEventTarget> mainTarget = do_QueryInterface(mainThread, &rv);
466  NS_ENSURE_SUCCESS(rv, rv);
467 
468  // the channel is not threadsafe, release it on the main thread instead
469  nsIChannel* channel = nsnull;
470  { /* scope */
471  nsAutoLock lock(mLock);
472  mChannel.forget(&channel);
473  }
474  if (channel) {
475  rv = NS_ProxyRelease(mainTarget, channel);
476  NS_ENSURE_SUCCESS(rv, rv);
477  }
478 
479  TRACE(("%s: successfully closed", __FUNCTION__));
480 
481  return NS_OK;
482 }
483 
484 /* sbGStreamerMessageHandler */
485 void
487 {
488  TRACE(("%s: received message %s", __FUNCTION__,
489  message ? GST_MESSAGE_TYPE_NAME(message) : "(null)"));
490  nsresult rv;
491  NS_ENSURE_TRUE(message, /* void */);
492  switch (GST_MESSAGE_TYPE(message)) {
493  case GST_MESSAGE_TAG:
494  HandleTagMessage(message);
495  break;
496  case GST_MESSAGE_STATE_CHANGED: {
497  nsAutoLock lock(mLock);
498  if (!mPipeline || mCompleted) {
499  // umm, we stopped, don't bother
500  return;
501  }
502  GstObject *src = GST_MESSAGE_SRC(message);
503  if (src == GST_OBJECT_CAST(mPipeline)) {
504  GstState oldState, newState, pendingState;
505  gst_message_parse_state_changed(message, &oldState, &newState,
506  &pendingState);
507  TRACE(("%s: state %s -> %s (-> %s)", __FUNCTION__,
508  gst_element_state_get_name(oldState),
509  gst_element_state_get_name(newState),
510  gst_element_state_get_name(pendingState)));
511  if (newState == GST_STATE_PAUSED) {
512  TRACE(("%s: Successfully paused", __FUNCTION__));
513  rv = FinalizeTags(PR_TRUE);
514  // don't hold the lock around Close()
515  nsresult rv2;
516  {
517  nsAutoUnlock unlock(mLock);
518  rv2 = Close();
519  }
520  mCompleted = PR_TRUE;
521  NS_ENSURE_SUCCESS(rv, /* void */);
522  NS_ENSURE_SUCCESS(rv2, /* void */);
523  }
524  }
525  break;
526  }
527  case GST_MESSAGE_ELEMENT: {
528  if (gst_is_missing_plugin_message(message)) {
529  /* If we got a missing plugin message about a missing video decoder,
530  we should still mark it as a video file */
531  const gchar *type = gst_structure_get_string(message->structure, "type");
532  if (type && !strcmp(type, "decoder")) {
533  /* Missing decoder: see if it's video */
534  const GValue *val = gst_structure_get_value (message->structure, "detail");
535  const GstCaps *caps = gst_value_get_caps (val);
536  GstStructure *structure = gst_caps_get_structure (caps, 0);
537  const gchar *capsname = gst_structure_get_name (structure);
538 
539  if (g_str_has_prefix(capsname, "video/")) {
540  mHasVideo = PR_TRUE;
541  }
542  }
543  }
544  break;
545  }
546  case GST_MESSAGE_ERROR: {
547  GError *gerror = NULL;
548  gchar *debugMessage = NULL;
549  gst_message_parse_error(message, &gerror, &debugMessage);
550  LOG(("%s: GStreamer error: %s / %s", __FUNCTION__, gerror->message, debugMessage));
551  g_error_free (gerror);
552  g_free (debugMessage);
553 
554  /* If we got an error from a video decoder, then it has undecodeable
555  video - but we should still mark it as a video file! */
556  if (GST_IS_ELEMENT (message->src)) {
557  GstElementClass *elementclass = GST_ELEMENT_CLASS (
558  G_OBJECT_GET_CLASS (message->src));
559  GstElementFactory *factory = elementclass->elementfactory;
560 
561  if (strstr (factory->details.klass, "Video") &&
562  strstr (factory->details.klass, "Decoder"))
563  {
564  mHasVideo = PR_TRUE;
565  }
566  }
567 
568  nsAutoLock lock(mLock);
569  if (!mCompleted) {
570  rv = FinalizeTags(PR_FALSE);
571  // don't hold the lock around Close()
572  nsresult rv2;
573  {
574  nsAutoUnlock unlock(mLock);
575  rv2 = Close();
576  }
577  mCompleted = PR_TRUE;
578  NS_ENSURE_SUCCESS(rv, /* void */);
579  NS_ENSURE_SUCCESS(rv2, /* void */);
580  }
581  break;
582  }
583  default:
584  break;
585  }
586 }
587 
588 PRBool
590 {
591  /* we never want to handle any messages synchronously */
592  return PR_FALSE;
593 }
594 
595 void
597  GstPad *newPad,
599 {
600  TRACE((__FUNCTION__));
601 
602  sbGstElement queue, sink, pipeline;
603  sbGstPad queueSink, oldPad, pad;
604 
605  NS_ENSURE_TRUE(self, /* void */); // can't use goto before mon is initialized
606  { /* scope */
607  nsAutoLock lock(self->mLock);
608  if (self->mCompleted) {
609  NS_WARNING("pad added after completion (or abort)");
610  return;
611  }
612  NS_ENSURE_TRUE(self->mPipeline, /* void */);
613  pipeline = GST_ELEMENT(gst_object_ref(self->mPipeline));
614  }
615  NS_ENSURE_TRUE(pipeline, /* void */);
616 
617  // attach a queue and a fakesink to the pad
618  queue = gst_element_factory_make("queue", NULL);
619  NS_ENSURE_TRUE(queue, /* void */);
620 
621  sink = gst_element_factory_make("fakesink", NULL);
622  NS_ENSURE_TRUE(sink, /* void */);
623 
624  // we want to keep references after we sink these in the sbin
625  gst_object_ref(queue.get());
626  gst_object_ref(sink.get());
627  gst_bin_add_many(GST_BIN_CAST(pipeline.get()), queue.get(), sink.get(), NULL);
628 
629  gst_element_set_state(queue.get(), GST_STATE_PAUSED);
630  gst_element_set_state(sink.get(), GST_STATE_PAUSED);
631 
632  queueSink = gst_element_get_static_pad(queue.get(), "sink");
633  NS_ENSURE_TRUE(queueSink, /* void */);
634 
635  GstPadLinkReturn linkResult;
636  linkResult = gst_pad_link(newPad, queueSink.get());
637  NS_ENSURE_TRUE(GST_PAD_LINK_OK == linkResult, /* void */);
638 
639  gboolean succeeded;
640  succeeded = gst_element_link_pads(queue.get(), "src", sink.get(), "sink");
641  NS_ENSURE_TRUE(succeeded, /* void */);
642 
643  // due to gnome bug 564863, when the new pad is a ghost pad we never get the
644  // notify::caps signal, and therefore never figure out the caps. Get the
645  // underlying pad (recusively) to work around this. Fixed in GStreamer
646  // 0.10.22.
647  pad = (GstPad*)gst_object_ref(newPad);
648  while (GST_IS_GHOST_PAD(pad.get())) {
649  oldPad = pad.forget();
650  pad = gst_ghost_pad_get_target(GST_GHOST_PAD(oldPad.get()));
651  }
652 
653  // manually check for fixed (already negotiated) caps; it will harmlessly
654  // return early if it's not already negotiated
655  on_pad_caps_changed(pad.get(), NULL, self);
656 
657  g_signal_connect(pad.get(), "notify::caps", G_CALLBACK(on_pad_caps_changed), self);
658 }
659 
660 static void
661 AddIntPropFromCaps(GstStructure *aStruct, const gchar *aFieldName,
662  const char* aPropName, sbIMutablePropertyArray *aProps)
663 {
664  gint value;
665  gboolean success = gst_structure_get_int(aStruct, aFieldName, &value);
666  if (success) {
667  nsString stringValue;
668  stringValue.AppendInt(value);
669  nsresult rv = aProps->AppendProperty(NS_ConvertASCIItoUTF16(aPropName),
670  stringValue);
671  NS_ENSURE_SUCCESS(rv, /* void */);
672  TRACE(("%s: %s = %s", __FUNCTION__, aPropName,
673  NS_LossyConvertUTF16toASCII(stringValue).get()));
674  }
675 }
676 
677 void
679  GParamSpec *pspec,
681 {
682  TRACE((__FUNCTION__));
683  nsAutoLock lock(self->mLock);
684  if (self->mCompleted) {
685  NS_WARNING("caps changed after completion (or abort)");
686  return;
687  }
688  GstStructure* capStruct = NULL;
689  sbGstCaps caps = gst_pad_get_negotiated_caps(pad);
690  if (!caps) {
691  // no negotiated caps yet, keep waiting
692  TRACE(("%s: no caps yet", __FUNCTION__));
693  return;
694  }
695 
696  char *capsString = gst_caps_to_string(caps.get());
697  const gchar *capName;
698  TRACE(("%s: caps are %s", __FUNCTION__, capsString));
699  if (capsString) {
700  g_free(capsString);
701  }
702 
703  NS_ENSURE_TRUE(gst_caps_get_size(caps.get()) > 0, /* void */);
704  capStruct = gst_caps_get_structure(caps.get(), 0);
705  NS_ENSURE_TRUE(capStruct, /* void */);
706  if (!self->mProperties) {
707  nsresult rv;
708  self->mProperties = do_CreateInstance(SB_MUTABLEPROPERTYARRAY_CONTRACTID,
709  &rv);
710  NS_ENSURE_SUCCESS(rv, /* void */);
711  }
712  NS_ENSURE_TRUE(self->mProperties, /* void */);
713 
714  capName = gst_structure_get_name(capStruct);
715  if (g_str_has_prefix(capName, "audio/")) {
716  AddIntPropFromCaps(capStruct, "channels",
717  SB_PROPERTY_CHANNELS, self->mProperties);
718  AddIntPropFromCaps(capStruct, "rate",
719  SB_PROPERTY_SAMPLERATE, self->mProperties);
720  self->mHasAudio = PR_TRUE;
721  } else if (g_str_has_prefix(capName, "video/")) {
722  self->mHasVideo = PR_TRUE;
723  }
724 }
725 
726 void
728 {
729  TRACE((__FUNCTION__));
730  GstTagList *tag_list = NULL;
731 
732  nsAutoLock lock(mLock);
733  if (mCompleted) {
734  NS_WARNING("tag message after completion (or abort)");
735  return;
736  }
737 
738  gst_message_parse_tag(message, &tag_list);
739 
740  if (mTags) {
741  GstTagList *newTags = gst_tag_list_merge (mTags, tag_list,
742  GST_TAG_MERGE_REPLACE);
743  gst_tag_list_free (mTags);
744  mTags = newTags;
745  }
746  else {
747  mTags = gst_tag_list_copy (tag_list);
748  }
749  gst_tag_list_free(tag_list);
750 }
751 
752 nsresult
754 {
755  TRACE(("%s[%p]", __FUNCTION__, this));
756  nsresult rv;
757 
758  // the caller is responsible for holding the lock :(
759 
760  if (!mProperties) {
761  mProperties = do_CreateInstance(SB_MUTABLEPROPERTYARRAY_CONTRACTID, &rv);
762  NS_ENSURE_SUCCESS(rv, rv);
763  }
764 
765  if (aSucceeded && mTags) {
766  nsCOMPtr<sbIPropertyArray> propArray;
767  rv = ConvertTagListToPropertyArray(mTags, getter_AddRefs(propArray));
768  NS_ENSURE_SUCCESS(rv, rv );
769 
770  // copy the tags over, don't clobber the old data
771  PRUint32 newLength;
772  rv = propArray->GetLength(&newLength);
773  NS_ENSURE_SUCCESS(rv, rv);
774 
775  for (PRUint32 i = 0 ; i < newLength; ++i) {
776  nsCOMPtr<sbIProperty> prop;
777  rv = propArray->GetPropertyAt(i, getter_AddRefs(prop));
778  NS_ENSURE_SUCCESS(rv, rv);
779 
780  nsString id, value;
781  rv = prop->GetId(id);
782  NS_ENSURE_SUCCESS(rv, rv);
783  rv = prop->GetValue(value);
784  NS_ENSURE_SUCCESS(rv, rv);
785 
786  rv = mProperties->AppendProperty(id, value);
787  NS_ENSURE_SUCCESS(rv, rv);
788  }
789  }
790 
791  nsString contentType;
792  if (mHasVideo) {
793  TRACE(("%s[%p]: file %s is video",
794  __FUNCTION__, this, mSpec.get()));
795  contentType = NS_LITERAL_STRING("video");
796  }
797  else if (mHasAudio) {
798  TRACE(("%s[%p]: file %s is audio",
799  __FUNCTION__, this, mSpec.get()));
800  contentType = NS_LITERAL_STRING("audio");
801  }
802  else {
803  // we didn't find either video or audio; fall back to file extension based
804  // detection of whether this file was likely to have contained some sort of
805  // video.
806  nsCOMPtr<sbIMediacoreCapabilities> caps;
807  rv = mFactory->GetCapabilities(getter_AddRefs(caps));
808  NS_ENSURE_SUCCESS(rv, rv);
809 
810  nsCOMPtr<nsIStringEnumerator> strings;
811  PRBool found = PR_FALSE;
812  rv = caps->GetVideoExtensions(getter_AddRefs(strings));
813  if (NS_SUCCEEDED(rv) && strings) {
814  found = HasExtensionInEnumerator(NS_ConvertUTF8toUTF16(mSpec), strings);
815  }
816  if (found) {
817  TRACE(("%s[%p]: file %s found via fallback to be video",
818  __FUNCTION__, this, mSpec.get()));
819  // this is a file with a video extension
820  contentType = NS_LITERAL_STRING("video");
821  }
822  else {
823  TRACE(("%s[%p]: file %s found via fallback to be not video",
824  __FUNCTION__, this, mSpec.get()));
825  }
826  }
827 
828  if (!contentType.IsEmpty()) {
829  rv = mProperties->AppendProperty(NS_LITERAL_STRING(SB_PROPERTY_CONTENTTYPE),
830  contentType);
831  NS_ENSURE_SUCCESS(rv, rv);
832  }
833 
834  return NS_OK;
835 }
836 
837 NS_IMETHODIMP
838 sbGStreamerMetadataHandler::Notify(nsITimer* aTimer)
839 {
840  TRACE((__FUNCTION__));
841 
842  nsresult rv;
843 
844  // hold a reference to the timer, since Close() releases it
845  nsCOMPtr<sbIMetadataHandler> kungFuDeathGrip(this);
846 
847  rv = Close();
848  nsAutoLock lock(mLock);
849  mCompleted = PR_TRUE;
850  mProperties = nsnull;
851  mHasAudio = PR_FALSE;
852  mHasVideo = PR_FALSE;
853  NS_ENSURE_SUCCESS(rv, rv);
854 
855  return NS_OK;
856 }
NS_IMPL_THREADSAFE_ISUPPORTS2(sbGStreamerMetadataHandler, sbIMetadataHandler, nsITimerCallback) sbGStreamerMetadataHandler
static void AddIntPropFromCaps(GstStructure *aStruct, const gchar *aFieldName, const char *aPropName, sbIMutablePropertyArray *aProps)
return NS_OK
#define SB_PROPERTY_SAMPLERATE
#define SB_PROPERTY_CHANNELS
menuItem id
Definition: FeedWriter.js:971
attribute nsIChannel channel
The object's nsIChannel.
function succeeded(ch, cx, status, data)
#define SB_GSTREAMER_METADATA_HANDLER_CONTRACTID
#define SB_MUTABLEPROPERTYARRAY_CONTRACTID
GstBusSyncReply SyncToAsyncDispatcher(GstBus *bus, GstMessage *message, gpointer data)
nsCOMPtr< sbIMutablePropertyArray > mProperties
#define SBGSTREAMERSERVICE_CONTRACTID
var strings
Definition: Info.js:46
An interface to carry around arrays of nsIProperty instances Note that implementations of the interfa...
nsresult FinalizeTags(PRBool aSucceeded)
#define TRACE(args)
#define SB_PROPERTY_CONTENTTYPE
const nsIChannel
this _dialogInput val(dateText)
virtual PRBool HandleSynchronousMessage(GstMessage *message)
An object capable of manipulating the metadata tags for a media file.
static PRBool HasExtensionInEnumerator(const nsAString &aHaystack, nsIStringEnumerator *aEnum)
GstMessage * message
nsCOMPtr< sbIMediacoreFactory > mFactory
#define METADATA_TIMEOUT
SB_AUTO_CLASS(sbGstElement, GstElement *,!!mValue, gst_object_unref(mValue), mValue=NULL)
function url(spec)
var uri
Definition: FeedWriter.js:1135
#define LOG(args)
StringArrayEnumerator prototype hasMore
countRef value
Definition: FeedWriter.js:1423
NS_DECL_ISUPPORTS NS_DECL_SBIMETADATAHANDLER virtual NS_DECL_NSITIMERCALLBACK void HandleMessage(GstMessage *message)
#define SB_GSTREAMERMEDIACOREFACTORY_CONTRACTID
var self
nsresult ConvertTagListToPropertyArray(GstTagList *taglist, sbIPropertyArray **aPropertyArray)
static void on_pad_caps_changed(GstPad *pad, GParamSpec *pspec, sbGStreamerMetadataHandler *data)
_getSelectedPageStyle s i
nsITimerCallback
_updateTextAndScrollDataForFrame aData
static void on_pad_added(GstElement *decodeBin, GstPad *newPad, sbGStreamerMetadataHandler *self)
void HandleTagMessage(GstMessage *message)