sbGStreamerMediacore.cpp
Go to the documentation of this file.
1 /*
2 //
3 // BEGIN SONGBIRD GPL
4 //
5 // This file is part of the Songbird web player.
6 //
7 // Copyright(c) 2005-2008 POTI, Inc.
8 // http://songbirdnest.com
9 //
10 // This file may be licensed under the terms of of the
11 // GNU General Public License Version 2 (the "GPL").
12 //
13 // Software distributed under the License is distributed
14 // on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either
15 // express or implied. See the GPL for the specific language
16 // governing rights and limitations.
17 //
18 // You should have received a copy of the GPL along with this
19 // program. If not, go to http://www.gnu.org/licenses/gpl.html
20 // or write to the Free Software Foundation, Inc.,
21 // 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
22 //
23 // END SONGBIRD GPL
24 //
25 */
26 
27 #include "sbGStreamerMediacore.h"
29 
30 #include <nsIFile.h>
31 #include <nsIFileURL.h>
32 #include <nsIInterfaceRequestorUtils.h>
33 #include <nsServiceManagerUtils.h>
34 #include <nsILocalFile.h>
35 #include <nsIURI.h>
36 #include <nsIURL.h>
37 #include <nsIRunnable.h>
38 #include <nsIIOService.h>
39 #include <nsIPrefBranch2.h>
40 #include <nsIObserver.h>
41 #include <nsThreadUtils.h>
42 #include <nsCOMPtr.h>
43 #include <prlog.h>
44 #include <prprf.h>
45 
46 // Required to crack open the DOM XUL Element and get a native window handle.
47 #include <nsIBaseWindow.h>
48 #include <nsIBoxObject.h>
49 #include <nsIDocument.h>
50 #include <nsIDOMAbstractView.h>
51 #include <nsIDOMEvent.h>
52 #include <nsIDocShellTreeItem.h>
53 #include <nsIDocShellTreeOwner.h>
54 #include <nsIDOMDocument.h>
55 #include <nsIDOMDocumentView.h>
56 #include <nsIDOMEventTarget.h>
57 #include <nsIDOMXULElement.h>
58 #include <nsIScriptGlobalObject.h>
59 #include <nsIWebNavigation.h>
60 #include <nsIWidget.h>
61 
62 #include <sbClassInfoUtils.h>
64 #include <sbVariantUtils.h>
66 #include <sbMediacoreError.h>
68 #include <sbStringBundle.h>
69 #include <sbErrorConsole.h>
70 #include <sbIMediacorePlaybackControl.h>
71 #include <sbIMediacoreManager.h>
72 #include <sbIGStreamerService.h>
73 #include <sbIMediaItem.h>
74 #include <sbStandardProperties.h>
75 #include <sbVideoBox.h>
76 
77 #include <gst/pbutils/missing-plugins.h>
78 
79 #include "nsNetUtil.h"
80 
81 #include <algorithm>
82 
83 #ifdef MOZ_WIDGET_GTK2
84 #include "sbGStreamerPlatformGDK.h"
85 #endif
86 
87 #ifdef XP_WIN
89 #endif
90 
91 #ifdef XP_MACOSX
92 #include "sbGStreamerPlatformOSX.h"
93 #endif
94 
95 #ifdef CreateEvent
96 #undef CreateEvent
97 #endif
98 
99 #define MAX_FILE_SIZE_FOR_ACCURATE_SEEK (20 * 1024 * 1024)
100 
101 #define EQUALIZER_FACTORY_NAME "equalizer-10bands"
102 
103 #define EQUALIZER_DEFAULT_BAND_COUNT \
104  sbBaseMediacoreMultibandEqualizer::EQUALIZER_BAND_COUNT_DEFAULT
105 
106 #define EQUALIZER_BANDS \
107  sbBaseMediacoreMultibandEqualizer::EQUALIZER_BANDS_10
108 
115 #ifdef PR_LOGGING
116 
117 static PRLogModuleInfo* gGStreamerMediacore =
118  PR_NewLogModule("sbGStreamerMediacore");
119 
120 #define LOG(args) \
121  if (gGStreamerMediacore) \
122  PR_LOG(gGStreamerMediacore, PR_LOG_WARNING, args)
123 
124 #define TRACE(args) \
125  if (gGStreamerMediacore) \
126  PR_LOG(gGStreamerMediacore, PR_LOG_DEBUG, args)
127 
128 #else /* PR_LOGGING */
129 
130 #define LOG(args) /* nothing */
131 #define TRACE(args) /* nothing */
132 
133 #endif /* PR_LOGGING */
134 
137 
139  sbIMediacore,
148  nsIObserver,
149  nsIClassInfo)
150 
151 NS_IMPL_CI_INTERFACE_GETTER8(sbGStreamerMediacore,
152  sbIMediacore,
153  sbIMediacoreMultibandEqualizer,
154  sbIMediacorePlaybackControl,
155  sbIMediacoreVideoWindow,
156  sbIMediacoreVolumeControl,
157  sbIMediacoreVotingParticipant,
158  sbIGStreamerMediacore,
159  sbIMediacoreEventTarget)
160 
161 NS_DECL_CLASSINFO(sbGStreamerMediacore)
162 NS_IMPL_THREADSAFE_CI(sbGStreamerMediacore)
163 
164 sbGStreamerMediacore::sbGStreamerMediacore() :
165  mMonitor(nsnull),
166  mIsVideoSupported(PR_FALSE),
167  mPipeline(nsnull),
168  mPlatformInterface(nsnull),
169  mPrefs(nsnull),
170  mReplaygainElement(nsnull),
171  mEqualizerElement(nsnull),
172  mTags(NULL),
173  mProperties(nsnull),
174  mStopped(PR_FALSE),
175  mBuffering(PR_FALSE),
176  mIsLive(PR_FALSE),
177  mMediacoreError(NULL),
178  mTargetState(GST_STATE_NULL),
179  mVideoDisabled(PR_FALSE),
180  mVideoSinkDescription(),
181  mAudioSinkDescription(),
182  mAudioSinkBufferTime(0),
183  mStreamingBufferSize(0),
184  mResourceIsLocal(PR_FALSE),
185  mResourceSize(-1),
186  mGaplessDisabled(PR_FALSE),
187  mPlayingGaplessly(PR_FALSE),
188  mAbortingPlayback(PR_FALSE),
189  mHasReachedPlaying(PR_FALSE),
190  mCurrentAudioCaps(NULL),
191  mAudioBinGhostPad(NULL),
192  mHasVideo(PR_FALSE),
193  mHasAudio(PR_FALSE)
194 {
195  mBaseEventTarget = new sbBaseMediacoreEventTarget(this);
196  NS_WARN_IF_FALSE(mBaseEventTarget,
197  "mBaseEventTarget is null, may be out of memory");
198 }
199 
201 {
202  if (mTags)
203  gst_tag_list_free(mTags);
204 
205  if (mReplaygainElement)
206  gst_object_unref (mReplaygainElement);
207 
208  if (mEqualizerElement)
209  gst_object_unref (mEqualizerElement);
210 
211  std::vector<GstElement *>::const_iterator it = mAudioFilters.begin();
212  for ( ; it < mAudioFilters.end(); ++it)
213  gst_object_unref (*it);
214 
215  if (mMonitor)
216  nsAutoMonitor::DestroyMonitor(mMonitor);
217 }
218 
219 nsresult
221 {
222  nsresult rv;
223 
224  mMonitor = nsAutoMonitor::NewMonitor("sbGStreamerMediacore::mMonitor");
225  NS_ENSURE_TRUE(mMonitor, NS_ERROR_OUT_OF_MEMORY);
226 
228  NS_ENSURE_SUCCESS(rv, rv);
229 
231  NS_ENSURE_SUCCESS(rv, rv);
232 
234  NS_ENSURE_SUCCESS(rv, rv);
235 
237  NS_ENSURE_SUCCESS(rv, rv);
238 
239  rv = InitPreferences();
240  NS_ENSURE_SUCCESS(rv, rv);
241 
242  nsCOMPtr<sbIMediacore> core = do_QueryInterface(
243  NS_ISUPPORTS_CAST(sbIMediacore *, this), &rv);
244  NS_ENSURE_SUCCESS(rv, rv);
245 
246 #if defined (MOZ_WIDGET_GTK2)
247  mIsVideoSupported = PR_TRUE;
249 #elif defined (XP_WIN)
250  mIsVideoSupported = PR_TRUE;
252 #elif defined (XP_MACOSX)
253  mIsVideoSupported = PR_TRUE;
255 #else
256  LOG(("No video backend available for this platform"));
257  mIsVideoSupported = PR_FALSE;
258 #endif
259 
260  return NS_OK;
261 }
262 
263 nsresult
265 {
266  nsresult rv;
267  mPrefs = do_ProxiedGetService("@mozilla.org/preferences-service;1", &rv);
268  NS_ENSURE_SUCCESS (rv, rv);
269 
270  rv = mPrefs->AddObserver("songbird.mediacore", this, PR_FALSE);
271  NS_ENSURE_SUCCESS(rv, rv);
272 
273  rv = ReadPreferences();
274  NS_ENSURE_SUCCESS(rv, rv);
275 
276  return NS_OK;
277 }
278 
279 nsresult
281 {
282  NS_ENSURE_STATE (mPrefs);
283  nsresult rv;
284 
285  rv = mPrefs->GetBoolPref("songbird.mediacore.gstreamer.disablevideo",
286  &mVideoDisabled);
287  if (rv == NS_ERROR_UNEXPECTED)
288  mVideoDisabled = PR_FALSE;
289  else
290  NS_ENSURE_SUCCESS(rv, rv);
291 
292  PRInt32 prefType;
293  const char *VIDEO_SINK_PREF = "songbird.mediacore.gstreamer.videosink";
294  const char *AUDIO_SINK_PREF = "songbird.mediacore.gstreamer.audiosink";
295 
296  rv = mPrefs->GetPrefType(VIDEO_SINK_PREF, &prefType);
297  NS_ENSURE_SUCCESS(rv, rv);
298  if (prefType == nsIPrefBranch::PREF_STRING) {
299  rv = mPrefs->GetCharPref(VIDEO_SINK_PREF,
300  getter_Copies(mVideoSinkDescription));
301  NS_ENSURE_SUCCESS(rv, rv);
302  }
303 
304  rv = mPrefs->GetPrefType(AUDIO_SINK_PREF, &prefType);
305  NS_ENSURE_SUCCESS(rv, rv);
306  if (prefType == nsIPrefBranch::PREF_STRING) {
307  rv = mPrefs->GetCharPref(AUDIO_SINK_PREF,
308  getter_Copies(mAudioSinkDescription));
309  NS_ENSURE_SUCCESS(rv, rv);
310  }
311 
312  /* In milliseconds */
313  const char *AUDIO_SINK_BUFFERTIME_PREF =
314  "songbird.mediacore.output.buffertime";
315  /* In kilobytes */
316  const char *STREAMING_BUFFERSIZE_PREF =
317  "songbird.mediacore.streaming.buffersize";
318 
319  /* Defaults if the prefs aren't present */
320  PRInt64 audioSinkBufferTime = 1000 * 1000; /* 1000 ms */
321  PRInt32 streamingBufferSize = 256 * 1024; /* 256 kB */
322 
323  rv = mPrefs->GetPrefType(AUDIO_SINK_BUFFERTIME_PREF, &prefType);
324  NS_ENSURE_SUCCESS(rv, rv);
325  if (prefType == nsIPrefBranch::PREF_INT) {
326  PRInt32 time = 0;
327  rv = mPrefs->GetIntPref(AUDIO_SINK_BUFFERTIME_PREF, &time);
328  NS_ENSURE_SUCCESS(rv, rv);
329 
330  // Only use the pref if it's a valid value.
331  if(time >= 100 || time <= 10000) {
332  // Convert to usecs
333  audioSinkBufferTime = time * 1000;
334  }
335  }
336 
337  rv = mPrefs->GetPrefType(STREAMING_BUFFERSIZE_PREF, &prefType);
338  NS_ENSURE_SUCCESS(rv, rv);
339  if (prefType == nsIPrefBranch::PREF_INT) {
340  rv = mPrefs->GetIntPref(STREAMING_BUFFERSIZE_PREF, &streamingBufferSize);
341  NS_ENSURE_SUCCESS(rv, rv);
342 
343  streamingBufferSize *= 1024; /* pref is in kB, we want it in B */
344  }
345 
346  mAudioSinkBufferTime = audioSinkBufferTime;
347  mStreamingBufferSize = streamingBufferSize;
348 
349  const char *NORMALIZATION_ENABLED_PREF =
350  "songbird.mediacore.normalization.enabled";
351  const char *NORMALIZATION_MODE_PREF =
352  "songbird.mediacore.normalization.preferredGain";
353  PRBool normalizationEnabled = PR_TRUE;
354  rv = mPrefs->GetPrefType(NORMALIZATION_ENABLED_PREF, &prefType);
355  NS_ENSURE_SUCCESS(rv, rv);
356  if (prefType == nsIPrefBranch::PREF_BOOL) {
357  rv = mPrefs->GetBoolPref(NORMALIZATION_ENABLED_PREF, &normalizationEnabled);
358  NS_ENSURE_SUCCESS(rv, rv);
359  }
360 
361  if (normalizationEnabled) {
362  if (!mReplaygainElement) {
363  mReplaygainElement = gst_element_factory_make ("rgvolume", NULL);
364 
365  // Ref and sink the object to take ownership; we'll keep track of it
366  // from here on.
367  gst_object_ref (mReplaygainElement);
368  gst_object_sink (mReplaygainElement);
369 
371  NS_ENSURE_SUCCESS(rv, rv);
372  }
373 
374  /* Now check the mode; set the appropriate property on the element */
375  nsCString normalizationMode;
376  rv = mPrefs->GetPrefType(NORMALIZATION_MODE_PREF, &prefType);
377  NS_ENSURE_SUCCESS(rv, rv);
378  if (prefType == nsIPrefBranch::PREF_STRING) {
379  rv = mPrefs->GetCharPref(NORMALIZATION_MODE_PREF,
380  getter_Copies(normalizationMode));
381  NS_ENSURE_SUCCESS(rv, rv);
382  }
383 
384  if (normalizationMode.EqualsLiteral("track")) {
385  g_object_set (mReplaygainElement, "album-mode", FALSE, NULL);
386  }
387  else {
388  g_object_set (mReplaygainElement, "album-mode", TRUE, NULL);
389  }
390  }
391  else {
392  if (mReplaygainElement) {
394  NS_ENSURE_SUCCESS(rv, rv);
395 
396  gst_object_unref (mReplaygainElement);
397  mReplaygainElement = NULL;
398  }
399  }
400 
401  return NS_OK;
402 }
403 
404 
405 // Utility methods
406 
407 /* static */ void
408 sbGStreamerMediacore::aboutToFinishHandler(GstElement *playbin, gpointer data)
409 {
410  sbGStreamerMediacore *core = static_cast<sbGStreamerMediacore*>(data);
412  return;
413 }
414 
415 GstElement *
416 sbGStreamerMediacore::CreateSinkFromPrefs(const char *aSinkDescription)
417 {
418  // Only try to create it if we have a non-null, non-zero-length description
419  if (aSinkDescription && *aSinkDescription)
420  {
421  GstElement *sink = gst_parse_bin_from_description (aSinkDescription,
422  TRUE, NULL);
423  // If parsing failed, sink is NULL; return it either way.
424  return sink;
425  }
426 
427  return NULL;
428 }
429 
430 nsresult
431 sbGStreamerMediacore::GetFileSize(nsIURI *aURI, PRInt64 *aFileSize)
432 {
433  nsresult rv;
434 
435  nsCOMPtr<nsIFileURL> fileUrl = do_QueryInterface(aURI, &rv);
436  if (rv != NS_ERROR_NO_INTERFACE) {
437  NS_ENSURE_SUCCESS (rv, rv);
438 
439  nsCOMPtr<nsIFile> file;
440  rv = fileUrl->GetFile(getter_AddRefs(file));
441  NS_ENSURE_SUCCESS(rv, rv);
442 
443  rv = file->GetFileSize(aFileSize);
444  NS_ENSURE_SUCCESS (rv, rv);
445  }
446  else {
447  // That's ok, not a local file.
448  return rv;
449  }
450 
451  return NS_OK;
452 }
453 
454 GstElement *
456 {
457  nsAutoMonitor lock(mMonitor);
458 
459  GstElement *videosink = CreateSinkFromPrefs(mVideoSinkDescription.get());
460 
461  if (mPlatformInterface)
462  videosink = mPlatformInterface->SetVideoSink(videosink);
463 
464  return videosink;
465 }
466 
467 GstElement *
469 {
470  nsAutoMonitor lock(mMonitor);
471 
472  GstElement *sinkbin = gst_bin_new ("audiosink-bin");
473  GstElement *audiosink = CreateSinkFromPrefs(mAudioSinkDescription.get());
474  GstPad *targetpad, *ghostpad;
475 
476  if (mPlatformInterface)
477  audiosink = mPlatformInterface->SetAudioSink(audiosink);
478 
479  /* audiosink is our actual sink, or something else close enough to it.
480  * Now, we build a bin looking like this, with each of the added audio
481  * filters in it:
482  *
483  * |-------------------------------------------------------------------|
484  * |audiosink-bin |
485  * | |
486  * | [-------] [------------] [-------] [------------] [---------] |
487  * |-[Filter1]--[audioconvert]--[Filter2]--[audioconvert]--[audiosink] |
488  * | [-------] [------------] [-------] [------------] [---------] |
489  * | |
490  * |-------------------------------------------------------------------|
491  *
492  */
493 
494  gst_bin_add ((GstBin *)sinkbin, audiosink);
495 
496  targetpad = gst_element_get_pad (audiosink, "sink");
497 
498  /* Add each filter, followed by an audioconvert. The first-added filter ends
499  * last in the pipeline, so we iterate in reverse.
500  */
501  std::vector<GstElement *>::const_reverse_iterator it = mAudioFilters.rbegin(),
502  end = mAudioFilters.rend();
503  for ( ; it != end; ++it)
504  {
505  GstElement *audioconvert = gst_element_factory_make ("audioconvert", NULL);
506  GstElement *filter = *it;
507  GstPad *srcpad, *sinkpad;
508 
509  gst_bin_add_many ((GstBin *)sinkbin, filter, audioconvert, NULL);
510 
511  srcpad = gst_element_get_pad (filter, "src");
512  sinkpad = gst_element_get_pad (audioconvert, "sink");
513  gst_pad_link (srcpad, sinkpad);
514  gst_object_unref (srcpad);
515  gst_object_unref (sinkpad);
516 
517  srcpad = gst_element_get_pad (audioconvert, "src");
518  gst_pad_link (srcpad, targetpad);
519  gst_object_unref (targetpad);
520  gst_object_unref (srcpad);
521 
522  targetpad = gst_element_get_pad (filter, "sink");
523  }
524 
525  // Now, targetpad is the left-most real pad in our bin. Ghost it to provide
526  // a sinkpad on our bin.
527  ghostpad = gst_ghost_pad_new ("sink", targetpad);
528  gst_element_add_pad (sinkbin, ghostpad);
529 
530  mAudioBinGhostPad = GST_GHOST_PAD (gst_object_ref (ghostpad));
531 
532  gst_object_unref (targetpad);
533 
534  return sinkbin;
535 }
536 
537 /* static */ void
538 sbGStreamerMediacore::currentAudioSetHelper(GObject* obj, GParamSpec* pspec,
539  sbGStreamerMediacore *core)
540 {
541  int current_audio;
542  GstPad *pad;
543 
544  // If a current audio stream has been selected, then we're playing audio
545  core->mHasAudio = PR_TRUE;
546 
547  /* Which audio stream has been activated? */
548  g_object_get(obj, "current-audio", &current_audio, NULL);
549  NS_ASSERTION(current_audio >= 0, "current audio is negative");
550 
551  /* Get the audio pad for this stream number */
552  g_signal_emit_by_name(obj, "get-audio-pad", current_audio, &pad);
553 
554  if (pad) {
555  GstCaps *caps;
556  caps = gst_pad_get_negotiated_caps(pad);
557  if (caps) {
558  core->OnAudioCapsSet(caps);
559  gst_caps_unref(caps);
560  }
561 
562  g_signal_connect(pad, "notify::caps",
563  G_CALLBACK(audioCapsSetHelper), core);
564 
565  gst_object_unref(pad);
566  }
567 }
568 
569 /* static */ void
570 sbGStreamerMediacore::audioCapsSetHelper(GObject* obj, GParamSpec* pspec,
571  sbGStreamerMediacore *core)
572 {
573  GstPad *pad = GST_PAD(obj);
574  GstCaps *caps = gst_pad_get_negotiated_caps(pad);
575 
576  if (caps) {
577  core->OnAudioCapsSet(caps);
578  gst_caps_unref (caps);
579  }
580 }
581 
582 /* static */ void
583 sbGStreamerMediacore::currentVideoSetHelper(GObject* obj, GParamSpec* pspec,
584  sbGStreamerMediacore *core)
585 {
586  int current_video;
587  GstPad *pad;
588 
589  // If a current video stream has been selected, then we're playing video
590  core->mHasVideo = PR_TRUE;
591 
592  /* Which video stream has been activated? */
593  g_object_get(obj, "current-video", &current_video, NULL);
594  NS_ASSERTION(current_video >= 0, "current video is negative");
595 
596  /* Get the video pad for this stream number */
597  g_signal_emit_by_name(obj, "get-video-pad", current_video, &pad);
598 
599  if (pad) {
600  GstCaps *caps;
601  caps = gst_pad_get_negotiated_caps(pad);
602  if (caps) {
603  core->OnVideoCapsSet(caps);
604  gst_caps_unref(caps);
605  }
606 
607  g_signal_connect(pad, "notify::caps",
608  G_CALLBACK(videoCapsSetHelper), core);
609 
610  gst_object_unref(pad);
611  }
612 }
613 
614 /* static */ void
615 sbGStreamerMediacore::videoCapsSetHelper(GObject* obj, GParamSpec* pspec,
616  sbGStreamerMediacore *core)
617 {
618  GstPad *pad = GST_PAD(obj);
619  GstCaps *caps = gst_pad_get_negotiated_caps(pad);
620 
621  if (caps) {
622  core->OnVideoCapsSet(caps);
623  gst_caps_unref (caps);
624  }
625 }
626 
627 nsresult
629 {
630  GstElement *pipeline = NULL;
631  nsAutoMonitor lock(mMonitor);
632  if (mPipeline)
633  pipeline = (GstElement *)g_object_ref (mPipeline);
634  lock.Exit();
635 
636  /* Do state-change with the lock dropped */
637  if (pipeline) {
638  gst_element_set_state (pipeline, GST_STATE_NULL);
639  gst_object_unref (pipeline);
640  }
641 
642  lock.Enter();
643  if (mPipeline) {
644  /* If we put any filters in the pipeline, remove them now, so that we can
645  * re-add them later to a different pipeline
646  */
647  std::vector<GstElement *>::const_iterator it = mAudioFilters.begin();
648  for ( ; it < mAudioFilters.end(); ++it)
649  {
650  GstElement *parent;
651  GstElement *filter = *it;
652 
653  parent = (GstElement *)gst_element_get_parent (filter);
654  if (parent) {
655  gst_bin_remove ((GstBin *)parent, filter);
656  gst_object_unref (parent);
657  }
658  }
659 
660  /* Work around bug in ghostpads (upstream #570910) by explicitly
661  * untargetting this ghostpad */
662  if (mAudioBinGhostPad) {
663  gst_ghost_pad_set_target (mAudioBinGhostPad, NULL);
664  gst_object_unref (mAudioBinGhostPad);
665  mAudioBinGhostPad = NULL;
666  }
667 
668  gst_object_unref (mPipeline);
669  mPipeline = nsnull;
670  }
671  if (mTags) {
672  gst_tag_list_free (mTags);
673  mTags = nsnull;
674  }
675  mProperties = nsnull;
676 
677  if (mCurrentAudioCaps) {
678  gst_caps_unref (mCurrentAudioCaps);
679  mCurrentAudioCaps = NULL;
680  }
681 
682  mStopped = PR_FALSE;
683  mBuffering = PR_FALSE;
684  mIsLive = PR_FALSE;
685  mMediacoreError = NULL;
686  mTargetState = GST_STATE_NULL;
687  mGaplessDisabled = PR_FALSE;
688  mPlayingGaplessly = PR_FALSE;
689  mAbortingPlayback = PR_FALSE;
690  mHasReachedPlaying = PR_FALSE;
691  mVideoSize = NULL;
692  mHasVideo = PR_FALSE;
693  mHasAudio = PR_FALSE;
694 
695  return NS_OK;
696 }
697 
698 nsresult
700 {
701  NS_ENSURE_ARG_POINTER(aPipeline);
702 
703  if (g_object_class_find_property(
704  G_OBJECT_GET_CLASS (aPipeline), "buffer-size"))
705  g_object_set (aPipeline, "buffer-size", mStreamingBufferSize, NULL);
706 
707  return NS_OK;
708 }
709 
710 // Set the property on the first applicable element we find. That's the first
711 // in sorted-iteration order, at minimal depth.
712 bool
714  const char *aPropertyName, gint64 aPropertyValue)
715 {
716  bool done = false;
717  bool ret = false;
718 
719  if (g_object_class_find_property(
720  G_OBJECT_GET_CLASS (aElement), aPropertyName))
721  {
722  g_object_set (aElement, aPropertyName, aPropertyValue, NULL);
723  return true;
724  }
725 
726  if (GST_IS_BIN (aElement)) {
727  // Iterate in sorted order, so we look at sinks first
728  GstIterator *it = gst_bin_iterate_sorted ((GstBin *)aElement);
729 
730  while (!done) {
731  gpointer data;
732  GstElement *child;
733  switch (gst_iterator_next (it, &data)) {
734  case GST_ITERATOR_OK:
735  child = GST_ELEMENT_CAST (data);
736  if (SetPropertyOnChild(child,
737  aPropertyName, aPropertyValue))
738  {
739  ret = true;
740  done = true;
741  }
742  gst_object_unref (child);
743  break;
744  case GST_ITERATOR_DONE:
745  done = TRUE;
746  break;
747  case GST_ITERATOR_RESYNC:
748  gst_iterator_resync (it);
749  break;
750  case GST_ITERATOR_ERROR:
751  done = true;
752  break;
753  }
754  }
755 
756  gst_iterator_free (it);
757  }
758 
759  return ret;
760 }
761 
762 nsresult
764 {
765  nsresult rv;
766  gint flags;
767 
768  // destroy pipeline will acquire the monitor.
769  rv = DestroyPipeline();
770  NS_ENSURE_SUCCESS (rv, rv);
771 
772  nsAutoMonitor lock(mMonitor);
773  mPipeline = gst_element_factory_make ("playbin2", "player");
774 
775  if (!mPipeline)
776  return NS_ERROR_FAILURE;
777 
778  if (mPlatformInterface) {
779  GstElement *audiosink = CreateAudioSink();
780 
781  // Set audio sink
782  g_object_set(mPipeline, "audio-sink", audiosink, NULL);
783 
784  // Set audio sink buffer time based on pref
785  SetPropertyOnChild(audiosink, "buffer-time",
786  (gint64)mAudioSinkBufferTime);
787 
788  if (!mVideoDisabled) {
789  GstElement *videosink = CreateVideoSink();
790  g_object_set(mPipeline, "video-sink", videosink, NULL);
791  }
792  }
793 
794  // Configure what to output - we want audio only, unless video
795  // is turned on
796  flags = 0x2 | 0x10; // audio | soft-volume
798  // Enable video only if we're set up for it is turned off. Also enable
799  // text (subtitles), which require a video window to display.
800  flags |= 0x1 | 0x4; // video | text
801  }
802  g_object_set (G_OBJECT(mPipeline), "flags", flags, NULL);
803 
804  GstBus *bus = gst_element_get_bus (mPipeline);
805 
806  // We want to receive state-changed messages when shutting down, so we
807  // need to turn off bus auto-flushing
808  g_object_set(mPipeline, "auto-flush-bus", FALSE, NULL);
809 
811  NS_ENSURE_SUCCESS (rv, rv);
812 
813  // Handle GStreamer messages synchronously, either directly or
814  // dispatching to the main thread.
815  gst_bus_set_sync_handler (bus, SyncToAsyncDispatcher,
816  static_cast<sbGStreamerMessageHandler*>(this));
817 
818  g_object_unref ((GObject *)bus);
819 
820  // Handle about-to-finish signal emitted by playbin2
821  g_signal_connect (mPipeline, "about-to-finish",
822  G_CALLBACK (aboutToFinishHandler), this);
823  // Get notified when the current audio/video stream changes.
824  // This will let us get information about the specific audio or video stream
825  // being played.
826  g_signal_connect (mPipeline, "notify::current-video",
827  G_CALLBACK (currentVideoSetHelper), this);
828  g_signal_connect (mPipeline, "notify::current-audio",
829  G_CALLBACK (currentAudioSetHelper), this);
830 
831  return NS_OK;
832 }
833 
835 {
836  GstMessageType msg_type;
837  msg_type = GST_MESSAGE_TYPE(aMessage);
838 
839  // Don't do message processing during an abort
840  if (mAbortingPlayback) {
841  TRACE(("Dropping message due to abort\n"));
842  return PR_TRUE;
843  }
844 
845  switch (msg_type) {
846  case GST_MESSAGE_ELEMENT: {
847  // Win32 and GDK use prepare-xwindow-id, OSX has its own private thing,
848  // have-ns-view
849  if (gst_structure_has_name(aMessage->structure, "prepare-xwindow-id") ||
850  gst_structure_has_name(aMessage->structure, "have-ns-view"))
851  {
853  {
855  mPlatformInterface->PrepareVideoWindow(aMessage);
856  }
857  return PR_TRUE;
858  }
859  }
860  default:
861  break;
862  }
863 
864  /* Return PR_FALSE since we haven't handled the message */
865  return PR_FALSE;
866 }
867 
869  nsIVariant *aData, sbIMediacoreError *aError)
870 {
871  nsresult rv;
872  nsCOMPtr<sbIMediacoreEvent> event;
874  aError,
875  aData,
876  this,
877  getter_AddRefs(event));
878  NS_ENSURE_SUCCESS(rv, /* void */);
879 
880  rv = DispatchEvent(event, PR_TRUE, nsnull);
881  NS_ENSURE_SUCCESS(rv, /* void */);
882 }
883 
885 {
886  LOG(("Handling about-to-finish signal"));
887 
888  nsAutoMonitor mon(mMonitor);
889 
890  // Never try to handle the next file if we've seen an error, or if gapless
891  // is disabled for this resource
893  LOG(("Ignoring about-to-finish signal"));
894  return;
895  }
896 
897  nsCOMPtr<sbIMediacoreSequencer> sequencer = mSequencer;
898  mon.Exit();
899 
900  if(!sequencer) {
901  return;
902  }
903 
904  nsCOMPtr<sbIMediaItem> item;
905  nsresult rv = sequencer->GetNextItem(getter_AddRefs(item));
906  NS_ENSURE_SUCCESS(rv, /*void*/ );
907  NS_ENSURE_TRUE(item, /*void*/);
908 
909  nsString contentURL;
910  rv = item->GetProperty(NS_LITERAL_STRING(SB_PROPERTY_CONTENTURL),
911  contentURL);
912  NS_ENSURE_SUCCESS(rv, /*void*/ );
913 
914  if(StringBeginsWith(contentURL, NS_LITERAL_STRING("file:"))) {
915  rv = sequencer->RequestHandleNextItem(this);
916  NS_ENSURE_SUCCESS(rv, /*void*/ );
917 
918  mon.Enter();
919 
920  // Clear old tags so we don't merge them with the new ones
921  if (mTags) {
922  gst_tag_list_free (mTags);
923  mTags = nsnull;
924  }
925  mProperties = nsnull;
926  mResourceIsLocal = PR_TRUE;
927 
928  nsCOMPtr<nsIURI> itemuri;
929  rv = item->GetContentSrc(getter_AddRefs(itemuri));
930  NS_ENSURE_SUCCESS (rv, /*void*/);
931 
932  rv = GetFileSize (itemuri, &mResourceSize);
933  NS_ENSURE_TRUE(mPipeline, /*void*/);
934 
935  nsCString uri = NS_ConvertUTF16toUTF8(contentURL);
936  LOG(("Setting URI to \"%s\"", uri.BeginReading()));
937 
938  /* Set the URI to play */
939  NS_ENSURE_TRUE(mPipeline, /*void*/);
940  g_object_set (G_OBJECT (mPipeline), "uri", uri.BeginReading(), NULL);
941  mCurrentUri = uri;
942  mUri = itemuri;
943 
944  mPlayingGaplessly = PR_TRUE;
945 
946  /* Ideally we wouldn't dispatch this until actual audio output of this new
947  * file has started, but playbin2 doesn't tell us about that yet */
949  }
950 
951  return;
952 }
953 
955 {
956  GstTagList *tag_list;
957  nsresult rv;
958 
959  LOG(("Handling tag message"));
960  gst_message_parse_tag(message, &tag_list);
961 
962  if (mTags) {
963  GstTagList *newTags = gst_tag_list_merge (mTags, tag_list,
964  GST_TAG_MERGE_REPLACE);
965  gst_tag_list_free (mTags);
966  mTags = newTags;
967  }
968  else
969  mTags = gst_tag_list_copy (tag_list);
970 
971  rv = ConvertTagListToPropertyArray(mTags, getter_AddRefs(mProperties));
972  gst_tag_list_free(tag_list);
973 
974  if (NS_SUCCEEDED (rv)) {
975  nsCOMPtr<nsISupports> properties = do_QueryInterface(mProperties, &rv);
976  NS_ENSURE_SUCCESS (rv, /* void */);
977  nsCOMPtr<nsIVariant> propVariant = sbNewVariant(properties).get();
979  }
980  else // Non-fatal, just log a message
981  LOG(("Failed to convert"));
982 }
983 
985 {
986  // Only listen to state-changed messages from top-level pipelines
987  if (GST_IS_PIPELINE (message->src))
988  {
989  GstState oldstate, newstate, pendingstate;
990  gst_message_parse_state_changed (message,
991  &oldstate, &newstate, &pendingstate);
992 
993  // Dispatch START, PAUSE, STOP/END (but only if it's our target state)
994  if (pendingstate == GST_STATE_VOID_PENDING && newstate == mTargetState) {
995  if (newstate == GST_STATE_PLAYING) {
996  mHasReachedPlaying = PR_TRUE;
998  }
999  else if (newstate == GST_STATE_PAUSED)
1001  else if (newstate == GST_STATE_NULL)
1002  {
1003  // Distinguish between 'stopped via API' and 'stopped due to error or
1004  // reaching EOS'
1005  if (mStopped) {
1007  }
1008  else {
1009  // If we stopped due to an error, send the actual error event now.
1010  if (mMediacoreError) {
1012  mMediacoreError);
1013  mMediacoreError = NULL;
1014  }
1016  }
1017  }
1018  }
1019  // We've reached our current pending state, but not our target state.
1020  else if (pendingstate == GST_STATE_VOID_PENDING)
1021  {
1022  // If we're not waiting for buffering to complete (where we handle
1023  // the state changes differently), then continue on to PLAYING.
1024  if (newstate == GST_STATE_PAUSED && mTargetState == GST_STATE_PLAYING &&
1025  !mBuffering)
1026  {
1027  gst_element_set_state (mPipeline, GST_STATE_PLAYING);
1028  }
1029  }
1030  }
1031 }
1032 
1034 {
1035  nsAutoMonitor lock(mMonitor);
1036 
1037  gint percent = 0;
1038  gint maxpercent;
1039  gst_message_parse_buffering (message, &percent);
1040  TRACE(("Buffering (%u percent done)", percent));
1041 
1042  // We don't want to handle buffering specially for live pipelines
1043  if (mIsLive)
1044  return;
1045 
1046  // 'maxpercent' is how much of our maximum buffer size we must fill before
1047  // we start playing. We want to be able to keep buffering more data (if the
1048  // server is sending it fast enough) even after we start playing - so we
1049  // start with maxpercent at 33, then increase it to 100 if we ever have to
1050  // rebuffer (i.e. return to buffering AFTER starting playback)
1051  if (mHasReachedPlaying)
1052  maxpercent = 100;
1053  else
1054  maxpercent = 33;
1055 
1056  /* If we receive buffering messages, go to PAUSED.
1057  * Then, return to PLAYING once we have sufficient buffering (which will be
1058  * before we actually hit PAUSED)
1059  */
1060  if (percent >= maxpercent && mBuffering) {
1061  mBuffering = PR_FALSE;
1062 
1063  if (mTargetState == GST_STATE_PLAYING) {
1064  TRACE(("Buffering complete, setting state to playing"));
1065  gst_element_set_state (mPipeline, GST_STATE_PLAYING);
1066  }
1067  else if (mTargetState == GST_STATE_PAUSED) {
1068  TRACE(("Buffering complete, setting state to paused"));
1070  }
1071  }
1072  else if (percent < maxpercent) {
1073  GstState cur_state;
1074  gst_element_get_state (mPipeline, &cur_state, NULL, 0);
1075 
1076  /* Only pause if we've already reached playing (this means we've underrun
1077  * the buffer and need to rebuffer) */
1078  if (!mBuffering && cur_state == GST_STATE_PLAYING) {
1079  TRACE(("Buffering... setting to paused"));
1080  gst_element_set_state (mPipeline, GST_STATE_PAUSED);
1081  mTargetState = GST_STATE_PLAYING;
1082 
1083  // And inform listeners that we've underrun */
1085  }
1086  mBuffering = PR_TRUE;
1087 
1088  // Inform listeners of current progress
1089  double bufferingProgress = (double)percent / (double)maxpercent;
1090  nsCOMPtr<nsIVariant> variant = sbNewVariant(bufferingProgress).get();
1092  }
1093 }
1094 
1095 // Demuxers (such as qtdemux) send redirect messages when the media
1096 // file itself redirects to another location. Handle these here.
1098 {
1099  const gchar *location;
1100  nsresult rv;
1101  nsCString uriString;
1102 
1103  location = gst_structure_get_string (message->structure, "new-location");
1104 
1105  if (location && *location) {
1106  if (strstr (location, "://") != NULL) {
1107  // Then we assume it's an absolute URL
1108  uriString = location;
1109  }
1110  else {
1111  TRACE (("Resolving redirect to '%s'", location));
1112 
1113  rv = mUri->Resolve(nsDependentCString(location), uriString);
1114  NS_ENSURE_SUCCESS (rv, /* void */ );
1115  }
1116 
1117  // Now create a URI from our string form.
1118  nsCOMPtr<nsIIOService> ioService = do_GetService(
1119  "@mozilla.org/network/io-service;1", &rv);
1120  NS_ENSURE_SUCCESS (rv, /* void */ );
1121 
1122  nsCOMPtr<nsIURI> finaluri;
1123  rv = ioService->NewURI(uriString, nsnull, nsnull,
1124  getter_AddRefs(finaluri));
1125  NS_ENSURE_SUCCESS (rv, /* void */ );
1126 
1127  PRBool isEqual;
1128  rv = finaluri->Equals(mUri, &isEqual);
1129  NS_ENSURE_SUCCESS (rv, /* void */ );
1130 
1131  // Don't loop forever redirecting to ourselves. If the URIs are the same,
1132  // then just ignore the redirect message.
1133  if (isEqual)
1134  return;
1135 
1136  // Ok, we have a new uri, and we're ready to use it...
1137  rv = SetUri(finaluri);
1138  NS_ENSURE_SUCCESS (rv, /* void */ );
1139 
1140  // Inform listeners that we've switched URI
1141  nsCOMPtr<nsIVariant> propVariant = sbNewVariant(finaluri).get();
1143 
1144  // And finally, attempt to play it
1145  rv = Play();
1146  NS_ENSURE_SUCCESS (rv, /* void */ );
1147  }
1148 }
1149 
1151 {
1152  nsCOMPtr<sbMediacoreError> error;
1153  gchar *debugMessage;
1154  nsString errorMessage;
1155  nsresult rv;
1156  nsString stringName;
1157 
1159  nsTArray<nsString> params;
1160 
1161  NS_ASSERTION(NS_IsMainThread(), "not on main thread");
1162 
1163  // Build an error message to output to the console
1164  // TODO: This is currently not localised (but we're probably not setting
1165  // things up right to get translated gstreamer messages anyway).
1166  debugMessage = gst_missing_plugin_message_get_description(message);
1167  if (debugMessage) {
1168  stringName = NS_LITERAL_STRING("mediacore.error.known_codec_not_found");
1169  params.AppendElement(NS_ConvertUTF8toUTF16(debugMessage));
1170  g_free(debugMessage);
1171  } else {
1172  stringName = NS_LITERAL_STRING("mediacore.error.codec_not_found");
1173  }
1174 
1175  if (!mMediacoreError) {
1176  nsCOMPtr<sbIMediacoreSequencer> sequencer;
1177  {
1178  nsAutoMonitor mon(mMonitor);
1179  sequencer = mSequencer;
1180  }
1181 
1182  if (sequencer) {
1183  // Got a valid sequencer, so grab the item's trackName to use in
1184  // the error message
1185  nsCOMPtr<sbIMediaItem> item;
1186  nsresult rv = sequencer->GetCurrentItem(getter_AddRefs(item));
1187  if (NS_SUCCEEDED(rv) && item) {
1188  nsString trackNameProp;
1189  rv = item->GetProperty(NS_LITERAL_STRING(SB_PROPERTY_TRACKNAME),
1190  trackNameProp);
1191  if (NS_SUCCEEDED(rv)) {
1192  nsAutoString stripped (trackNameProp);
1193  CompressWhitespace(stripped);
1194  if (!stripped.IsEmpty()) {
1195  NS_NEWXPCOM(error, sbMediacoreError);
1196  if (NS_SUCCEEDED(rv)) {
1197  params.InsertElementAt(0, trackNameProp);
1198  errorMessage = bundle.Format(stringName, params);
1200  errorMessage);
1201  }
1202  }
1203  }
1204  }
1205  }
1206 
1207  // We'll hit this if we didn't find the sequencer, or if we got the
1208  // sequencer, but had an empty trackName
1209  if (!error) {
1210  // If we couldn't find the sequencer, then use the item's file path
1211  // to generate the error message
1212  nsCOMPtr<nsIURI> uri;
1213  rv = GetUri(getter_AddRefs(uri));
1214  NS_ENSURE_SUCCESS(rv, /* void */);
1215 
1216  nsCOMPtr<nsIFileURL> url = do_QueryInterface(uri, &rv);
1217  if (NS_SUCCEEDED(rv))
1218  {
1219  nsCOMPtr<nsIFile> file;
1220  nsString path;
1221 
1222  rv = url->GetFile(getter_AddRefs(file));
1223  if (NS_SUCCEEDED(rv))
1224  {
1225  rv = file->GetPath(path);
1226 
1227  if (NS_SUCCEEDED(rv)) {
1228  NS_NEWXPCOM(error, sbMediacoreError);
1229  if (NS_SUCCEEDED(rv)) {
1230  params.InsertElementAt(0, path);
1231  errorMessage = bundle.Format(stringName, params);
1233  errorMessage);
1234  }
1235  }
1236  }
1237  }
1238 
1239  if (NS_FAILED(rv)) // not an else, so that it serves as a fallback
1240  {
1241  nsCString temp;
1242  nsString spec;
1243 
1244  rv = uri->GetSpec(temp);
1245  if (NS_SUCCEEDED(rv))
1246  spec = NS_ConvertUTF8toUTF16(temp);
1247  else
1248  spec = NS_ConvertUTF8toUTF16(mCurrentUri);
1249 
1250  NS_NEWXPCOM(error, sbMediacoreError);
1251  if (NS_SUCCEEDED(rv)) {
1252  params.InsertElementAt(0, spec);
1253  errorMessage = bundle.Format(stringName, params);
1255  errorMessage);
1256  }
1257  }
1258 
1259  NS_ENSURE_SUCCESS(rv, /* void */);
1260  }
1261 
1262  // We don't actually send the error right now. If we did, the sequencer
1263  // might tell us to continue on to the next file before we've finished
1264  // processing shutdown for this stream.
1265  // Instead, we just cache it here, and actually process it once shutdown
1266  // is complete.
1267  mMediacoreError = error;
1268  }
1269 
1270  // Then, shut down the pipeline, which will cause
1271  // a STREAM_END event to be fired. Immediately before firing that, we'll
1272  // send our error message.
1273  nsAutoMonitor lock(mMonitor);
1274  mTargetState = GST_STATE_NULL;
1275  GstElement *pipeline = (GstElement *)g_object_ref (mPipeline);
1276  lock.Exit();
1277 
1278  gst_element_set_state (pipeline, GST_STATE_NULL);
1279  g_object_unref (pipeline);
1280 
1281  // Log the error message
1282  sbErrorConsole::Error("Mediacore:GStreamer", errorMessage);
1283 }
1284 
1286 {
1287  nsAutoMonitor lock(mMonitor);
1288  GstElement *pipeline = (GstElement *)g_object_ref (mPipeline);
1289  mTargetState = GST_STATE_NULL;
1290  lock.Exit();
1291 
1292  // Shut down the pipeline. This will cause us to send a STREAM_END
1293  // event when we get the state-changed message to GST_STATE_NULL
1294  gst_element_set_state (pipeline, GST_STATE_NULL);
1295  g_object_unref (pipeline);
1296 }
1297 
1299 {
1300  GError *gerror = NULL;
1301  nsString errormessage;
1302  nsCOMPtr<sbIMediacoreError> error;
1303  nsCOMPtr<sbIMediacoreEvent> event;
1304  gchar *debugMessage;
1305  nsresult rv = NS_ERROR_UNEXPECTED;
1306 
1307  NS_ASSERTION(NS_IsMainThread(), "not on main thread");
1308 
1309  gst_message_parse_error(message, &gerror, &debugMessage);
1310 
1311  if (!mMediacoreError) {
1312 
1313  // Try and fetch track name from sequencer information.
1314  nsCOMPtr<sbIMediacoreSequencer> sequencer;
1315  {
1316  nsAutoMonitor mon(mMonitor);
1317  sequencer = mSequencer;
1318  }
1319 
1320  if (sequencer) {
1321  // Got a valid sequencer, so grab the item's trackName to use in
1322  // the error message
1323  nsCOMPtr<sbIMediaItem> item;
1324  rv = sequencer->GetCurrentItem(getter_AddRefs(item));
1325  if (NS_SUCCEEDED(rv) && item) {
1326  nsString trackNameProp;
1327  rv = item->GetProperty(NS_LITERAL_STRING(SB_PROPERTY_TRACKNAME),
1328  trackNameProp);
1329  if (NS_SUCCEEDED(rv)) {
1330  nsAutoString stripped (trackNameProp);
1331  CompressWhitespace(stripped);
1332  rv = GetMediacoreErrorFromGstError(gerror, stripped,
1334  getter_AddRefs(error));
1335  }
1336  }
1337  }
1338 
1339  //
1340  // If there was no sequencer or there was an error while fetching the
1341  // track name we'll fall back to using the file path.
1342  //
1343  if (NS_FAILED(rv)) {
1344  // Create an error for later dispatch.
1345  nsCOMPtr<nsIURI> uri;
1346  rv = GetUri(getter_AddRefs(uri));
1347  NS_ENSURE_SUCCESS(rv, /* void */);
1348 
1349  nsCOMPtr<nsIFileURL> url = do_QueryInterface(uri, &rv);
1350  if (NS_SUCCEEDED(rv))
1351  {
1352  nsCOMPtr<nsIFile> file;
1353  nsString path;
1354 
1355  rv = url->GetFile(getter_AddRefs(file));
1356  if (NS_SUCCEEDED(rv))
1357  {
1358  rv = file->GetPath(path);
1359 
1360  if (NS_SUCCEEDED(rv))
1361  rv = GetMediacoreErrorFromGstError(gerror, path,
1363  getter_AddRefs(error));
1364  }
1365  }
1366 
1367  //
1368  // Blast! Still no information for the user, use the URI Spec as a last
1369  // ditch effort to give them useful information.
1370  //
1371  if (NS_FAILED(rv)) // not an else, so that it serves as a fallback
1372  {
1373  nsCString temp;
1374  nsString spec;
1375 
1376  rv = uri->GetSpec(temp);
1377  if (NS_SUCCEEDED(rv))
1378  spec = NS_ConvertUTF8toUTF16(temp);
1379  else
1380  spec = NS_ConvertUTF8toUTF16(mCurrentUri);
1381 
1382  rv = GetMediacoreErrorFromGstError(gerror, spec,
1384  getter_AddRefs(error));
1385  }
1386  }
1387 
1388  NS_ENSURE_SUCCESS(rv, /* void */);
1389 
1390  // We don't actually send the error right now. If we did, the sequencer
1391  // might tell us to continue on to the next file before we've finished
1392  // processing shutdown for this stream.
1393  // Instead, we just cache it here, and actually process it once shutdown
1394  // is complete.
1395  mMediacoreError = error;
1396  }
1397 
1398  // Build an error message to output to the console
1399  // TODO: This is currently not localised (but we're probably not setting
1400  // things up right to get translated gstreamer messages anyway).
1401  nsString errmessage = NS_LITERAL_STRING("GStreamer error: ");
1402  errmessage.Append(NS_ConvertUTF8toUTF16(gerror->message));
1403  errmessage.Append(NS_LITERAL_STRING(" Additional information: "));
1404  errmessage.Append(NS_ConvertUTF8toUTF16(debugMessage));
1405 
1406  g_error_free (gerror);
1407  g_free (debugMessage);
1408 
1409  // Then, shut down the pipeline, which will cause
1410  // a STREAM_END event to be fired. Immediately before firing that, we'll
1411  // send our error message.
1412  nsAutoMonitor lock(mMonitor);
1413  mTargetState = GST_STATE_NULL;
1414  GstElement *pipeline = (GstElement *)g_object_ref (mPipeline);
1415  lock.Exit();
1416 
1417  gst_element_set_state (pipeline, GST_STATE_NULL);
1418  g_object_unref (pipeline);
1419 
1420  // Log the error message
1421  sbErrorConsole::Error("Mediacore:GStreamer", errmessage);
1422 }
1423 
1425 {
1426  GError *gerror = NULL;
1427  gchar *debugMessage;
1428 
1429  NS_ASSERTION(NS_IsMainThread(), "not on main thread");
1430 
1431  gst_message_parse_warning(message, &gerror, &debugMessage);
1432 
1433  // TODO: This is currently not localised (but we're probably not setting
1434  // things up right to get translated gstreamer messages anyway).
1435  nsString warning = NS_LITERAL_STRING("GStreamer warning: ");
1436  warning.Append(NS_ConvertUTF8toUTF16(gerror->message));
1437  warning.Append(NS_LITERAL_STRING(" Additional information: "));
1438  warning.Append(NS_ConvertUTF8toUTF16(debugMessage));
1439 
1440  g_error_free (gerror);
1441  g_free (debugMessage);
1442 
1443  sbErrorConsole::Error("Mediacore:GStreamer", warning);
1444 }
1445 
1446 /* Dispatch messages based on type.
1447  * For ELEMENT messages, further introspect the exact meaning for
1448  * dispatch
1449  */
1451 {
1452  GstMessageType msg_type;
1453  msg_type = GST_MESSAGE_TYPE(message);
1454 
1455  LOG(("Got message: %s", gst_message_type_get_name(msg_type)));
1456 
1457  switch (msg_type) {
1458  case GST_MESSAGE_STATE_CHANGED:
1459  HandleStateChangedMessage(message);
1460  break;
1461  case GST_MESSAGE_TAG:
1462  HandleTagMessage(message);
1463  break;
1464  case GST_MESSAGE_ERROR:
1465  HandleErrorMessage(message);
1466  break;
1467  case GST_MESSAGE_WARNING:
1468  HandleWarningMessage(message);
1469  break;
1470  case GST_MESSAGE_EOS:
1471  HandleEOSMessage(message);
1472  break;
1473  case GST_MESSAGE_BUFFERING:
1474  HandleBufferingMessage(message);
1475  case GST_MESSAGE_ELEMENT: {
1476  if (gst_structure_has_name (message->structure, "redirect")) {
1477  HandleRedirectMessage(message);
1478  } else if (gst_is_missing_plugin_message(message)) {
1479  HandleMissingPluginMessage(message);
1480  }
1481  break;
1482  }
1483  default:
1484  LOG(("Got message: %s", gst_message_type_get_name(msg_type)));
1485  break;
1486  }
1487 }
1488 
1489 /* Main-thread only! */
1491 {
1492  nsresult rv;
1493  PRUint32 videoWidth = 0;
1494  PRUint32 videoHeight = 0;
1495 
1496  nsCOMPtr<sbIMediacoreManager> mediacoreManager =
1497  do_GetService(SB_MEDIACOREMANAGER_CONTRACTID, &rv);
1498  NS_ENSURE_SUCCESS(rv, /* void */);
1499 
1500  if (mVideoSize) {
1501  rv = mVideoSize->GetWidth(&videoWidth);
1502  NS_ENSURE_SUCCESS(rv, /* void */);
1503  rv = mVideoSize->GetHeight(&videoHeight);
1504  NS_ENSURE_SUCCESS(rv, /* void */);
1505 
1506  PRUint32 parNum, parDenom;
1507  rv = mVideoSize->GetParNumerator(&parNum);
1508  NS_ENSURE_SUCCESS(rv, /* void */);
1509  rv = mVideoSize->GetParDenominator(&parDenom);
1510  NS_ENSURE_SUCCESS(rv, /* void */);
1511 
1512  // We adjust width rather than height as adjusting height tends to make
1513  // interlacing artifacts worse.
1514  videoWidth = videoWidth * parNum / parDenom;
1515  }
1516 
1517  nsCOMPtr<sbIMediacoreVideoWindow> videoWindow;
1518  rv = mediacoreManager->GetPrimaryVideoWindow(PR_TRUE, videoWidth, videoHeight,
1519  getter_AddRefs(videoWindow));
1520  NS_ENSURE_SUCCESS(rv, /* void */);
1521 
1522  if (videoWindow != NULL) {
1523  nsCOMPtr<nsIDOMXULElement> videoDOMElement;
1524  rv = videoWindow->GetVideoWindow(getter_AddRefs(videoDOMElement));
1525  NS_ENSURE_SUCCESS(rv, /* void */);
1526 
1527  /* Set our video window. This will ensure that SetVideoBox
1528  is called on the platform interface, so it can then use that. */
1529  rv = SetVideoWindow(videoDOMElement);
1530  NS_ENSURE_SUCCESS(rv, /* void */);
1531 
1533  sbNewVariant(mVideoSize).get());
1534  }
1535 }
1536 
1537 void
1539 {
1540  GstStructure *s;
1541  gint pixelAspectRatioN, pixelAspectRatioD;
1542  gint videoWidth, videoHeight;
1543 
1544  s = gst_caps_get_structure(caps, 0);
1545  if(s) {
1546  gst_structure_get_int(s, "width", &videoWidth);
1547  gst_structure_get_int(s, "height", &videoHeight);
1548 
1549  /* pixel-aspect-ratio is optional */
1550  const GValue* par = gst_structure_get_value(s, "pixel-aspect-ratio");
1551  if (par) {
1552  pixelAspectRatioN = gst_value_get_fraction_numerator(par);
1553  pixelAspectRatioD = gst_value_get_fraction_denominator(par);
1554  }
1555  else {
1556  /* PAR not set; default to square pixels */
1557  pixelAspectRatioN = pixelAspectRatioD = 1;
1558  }
1559 
1560  if (mPlatformInterface) {
1561  int num = videoWidth * pixelAspectRatioN;
1562  int denom = videoHeight * pixelAspectRatioD;
1563  mPlatformInterface->SetDisplayAspectRatio(num, denom);
1564  }
1565  }
1566  else {
1567  // Should never be reachable, but let's be defensive here...
1568  videoHeight = 240;
1569  videoWidth = 320;
1570  pixelAspectRatioN = pixelAspectRatioD = 0;
1571  }
1572 
1573  // We don't do gapless playback if video is involved. If we're already in
1574  // gapless mode (this is the 2nd-or-subsequent resource), abort and try again
1575  // in non-gapless mode. If it's the first resource, just flag that we
1576  // shouldn't attempt gapless playback.
1577  if (mPlayingGaplessly) {
1578  // Ignore all messages while we're aborting playback
1579  mAbortingPlayback = PR_TRUE;
1580  nsCOMPtr<nsIRunnable> abort =
1581  NS_NEW_RUNNABLE_METHOD(sbGStreamerMediacore, this,
1583  NS_DispatchToMainThread(abort);
1584  }
1585 
1586  mGaplessDisabled = PR_TRUE;
1587 
1588  // Send VIDEO_SIZE_CHANGED event
1589  nsRefPtr<sbVideoBox> videoBox;
1590  NS_NEWXPCOM(videoBox, sbVideoBox);
1591  NS_ENSURE_TRUE(videoBox, /*void*/);
1592 
1593  nsresult rv = videoBox->Init(videoWidth,
1594  videoHeight,
1595  pixelAspectRatioN,
1596  pixelAspectRatioD);
1597  NS_ENSURE_SUCCESS(rv, /*void*/);
1598 
1600  sbNewVariant(videoBox).get());
1601 
1602  mVideoSize = do_QueryInterface(videoBox, &rv);
1603  NS_ENSURE_SUCCESS(rv, /*void*/);
1604 }
1605 
1606 void
1608 {
1610  !gst_caps_is_equal_fixed (caps, mCurrentAudioCaps))
1611  {
1612  // Ignore all messages while we're aborting playback
1613  mAbortingPlayback = PR_TRUE;
1614  nsCOMPtr<nsIRunnable> abort =
1615  NS_NEW_RUNNABLE_METHOD(sbGStreamerMediacore, this,
1617  NS_DispatchToMainThread(abort);
1618  }
1619 
1620  if (mCurrentAudioCaps) {
1621  gst_caps_unref (mCurrentAudioCaps);
1622  }
1623  mCurrentAudioCaps = gst_caps_ref (caps);
1624 }
1625 
1627 {
1628  nsresult rv = Stop();
1629  NS_ENSURE_SUCCESS (rv, /* void */);
1630  mAbortingPlayback = PR_FALSE;
1631 
1632  rv = CreatePlaybackPipeline();
1633  NS_ENSURE_SUCCESS (rv, /* void */);
1634 
1635  g_object_set (G_OBJECT (mPipeline), "uri", mCurrentUri.get(), NULL);
1636 
1637  // Apply volume/mute to our new pipeline
1638  if (mMute) {
1639  g_object_set (G_OBJECT (mPipeline), "volume", 0.0, NULL);
1640  }
1641  else {
1642  g_object_set (G_OBJECT (mPipeline), "volume", mVolume, NULL);
1643  }
1644 
1645  rv = Play();
1646  NS_ENSURE_SUCCESS (rv, /* void */);
1647 }
1648 
1649 //-----------------------------------------------------------------------------
1650 // sbBaseMediacore
1651 //-----------------------------------------------------------------------------
1652 
1653 /*virtual*/ nsresult
1655 {
1656  nsresult rv;
1657 
1658  // Ensure the service component is loaded; it initialises GStreamer for us.
1659  nsCOMPtr<sbIGStreamerService> service =
1660  do_GetService(SBGSTREAMERSERVICE_CONTRACTID, &rv);
1661  NS_ENSURE_SUCCESS(rv, rv);
1662 
1663  return NS_OK;
1664 }
1665 
1666 /*virtual*/ nsresult
1668 {
1669  // XXXAus: Implement this when implementing the default sequencer!
1670  return NS_ERROR_NOT_IMPLEMENTED;
1671 }
1672 
1673 /*virtual*/ nsresult
1675 {
1676  nsAutoMonitor lock(mMonitor);
1677 
1678  if (mPipeline) {
1679  LOG (("Destroying pipeline on shutdown"));
1680  DestroyPipeline();
1681  }
1682 
1683  if (mPrefs) {
1684  nsresult rv = mPrefs->RemoveObserver("songbird.mediacore.gstreamer", this);
1685  NS_ENSURE_SUCCESS(rv, rv);
1686  }
1687 
1688  return NS_OK;
1689 }
1690 
1691 //-----------------------------------------------------------------------------
1692 // sbBaseMediacoreMultibandEqualizer
1693 //-----------------------------------------------------------------------------
1694 /*virtual*/ nsresult
1696 {
1698  gst_element_factory_make (EQUALIZER_FACTORY_NAME, NULL);
1699  NS_WARN_IF_FALSE(mEqualizerElement, "No support for equalizer.");
1700 
1701  if (mEqualizerElement) {
1702  // Ref and sink the object to take ownership; we'll keep track of it
1703  // from here on.
1704  gst_object_ref (mEqualizerElement);
1705  gst_object_sink (mEqualizerElement);
1706 
1707  // Set the bands to the frequencies we want
1708  char band[16] = {0};
1709 
1710  GValue freqVal = {0};
1711  g_value_init (&freqVal, G_TYPE_DOUBLE);
1712 
1713  for(PRUint32 i = 0; i < EQUALIZER_DEFAULT_BAND_COUNT; ++i) {
1714  PR_snprintf (band, 16, "band%i::freq", i);
1715  g_value_set_double (&freqVal, EQUALIZER_BANDS[i]);
1716  gst_child_proxy_set_property (GST_OBJECT (mEqualizerElement),
1717  band,
1718  &freqVal);
1719  }
1720 
1721  g_value_unset (&freqVal);
1722 
1724  }
1725 
1726  return NS_OK;
1727 }
1728 
1729 /*virtual*/ nsresult
1731 {
1732  // Not necessarily an error if we don't have an Equalizer Element.
1733  // The plugin may simply be missing from the user's installation.
1734  if(!mEqualizerElement) {
1735  return NS_OK;
1736  }
1737 
1738  // Disable gain on bands if we're being disabled.
1739  if(!aEqEnabled) {
1740  char band[8] = {0};
1741  double bandGain = 0;
1742 
1743  nsAutoMonitor lock(mMonitor);
1744 
1745  for(PRUint32 i = 0; i < EQUALIZER_DEFAULT_BAND_COUNT; ++i) {
1746  PR_snprintf (band, 8, "band%i", i);
1747  g_object_set (G_OBJECT (mEqualizerElement), band, bandGain, NULL);
1748  }
1749  }
1750 
1751  return NS_OK;
1752 }
1753 
1754 /*virtual*/ nsresult
1756 {
1757  NS_ENSURE_ARG_POINTER(aBandCount);
1758 
1759  *aBandCount = 0;
1760 
1761  // Not necessarily an error if we don't have an Equalizer Element.
1762  // The plugin may simply be missing from the user's installation.
1763  if (!mEqualizerElement) {
1764  return NS_OK;
1765  }
1766 
1767  *aBandCount = EQUALIZER_DEFAULT_BAND_COUNT;
1768 
1769  return NS_OK;
1770 }
1771 
1772 /*virtual*/ nsresult
1774 {
1775  NS_ENSURE_ARG_POINTER(aBand);
1776  NS_ENSURE_ARG_RANGE(aBandIndex, 0, EQUALIZER_DEFAULT_BAND_COUNT - 1);
1777 
1778  // Not necessarily an error if we don't have an Equalizer Element.
1779  // The plugin may simply be missing from the user's installation.
1780  if (!mEqualizerElement) {
1781  return NS_OK;
1782  }
1783 
1784  char band[8] = {0};
1785  PR_snprintf(band, 8, "band%i", aBandIndex);
1786 
1787  gdouble bandGain = 0.0;
1788  g_object_get (G_OBJECT (mEqualizerElement), band, &bandGain, NULL);
1789 
1790  nsresult rv = aBand->Init(aBandIndex, EQUALIZER_BANDS[aBandIndex], bandGain);
1791  NS_ENSURE_SUCCESS(rv, rv);
1792 
1793  return NS_OK;
1794 }
1795 
1796 /*virtual*/ nsresult
1798 {
1799  NS_ENSURE_ARG_POINTER(aBand);
1800 
1801  // Not necessarily an error if we don't have an Equalizer Element.
1802  // The plugin may simply be missing from the user's installation.
1803  if (!mEqualizerElement) {
1804  return NS_OK;
1805  }
1806 
1807  PRUint32 bandIndex = 0;
1808  nsresult rv = aBand->GetIndex(&bandIndex);
1809  NS_ENSURE_SUCCESS(rv, rv);
1810 
1811  double bandGain = 0.0;
1812  rv = aBand->GetGain(&bandGain);
1813  NS_ENSURE_SUCCESS(rv, rv);
1814 
1815  // Clamp and recenter.
1816  bandGain = (12.0 * SB_ClampDouble(bandGain, -1.0, 1.0));
1817 
1818  char band[8] = {0};
1819  PR_snprintf(band, 8, "band%i", bandIndex);
1820 
1821  nsAutoMonitor lock(mMonitor);
1822  g_object_set (G_OBJECT (mEqualizerElement), band, bandGain, NULL);
1823 
1824  return NS_OK;
1825 }
1826 
1827 //-----------------------------------------------------------------------------
1828 // sbBaseMediacorePlaybackControl
1829 //-----------------------------------------------------------------------------
1830 
1831 /*virtual*/ nsresult
1833 {
1834  return NS_OK;
1835 }
1836 
1837 /*virtual*/ nsresult
1839 {
1840  nsCAutoString spec;
1841  nsresult rv;
1842 
1843  // createplaybackpipeline will acquire the monitor.
1844  rv = CreatePlaybackPipeline();
1845  NS_ENSURE_SUCCESS (rv,rv);
1846 
1847  nsAutoMonitor lock(mMonitor);
1848 
1849  rv = aURI->GetSpec(spec);
1850  NS_ENSURE_SUCCESS(rv, rv);
1851 
1852  rv = GetFileSize (aURI, &mResourceSize);
1853  if (rv == NS_ERROR_NO_INTERFACE) {
1854  // Not being a file is fine - that's just something non-local
1855  mResourceIsLocal = PR_FALSE;
1856  mResourceSize = -1;
1857  }
1858  else
1859  mResourceIsLocal = PR_TRUE;
1860 
1861  LOG(("Setting URI to \"%s\"", spec.get()));
1862 
1863  /* Set the URI to play */
1864  g_object_set (G_OBJECT (mPipeline), "uri", spec.get(), NULL);
1865  mCurrentUri = spec;
1866 
1867  return NS_OK;
1868 }
1869 
1870 /*virtual*/ nsresult
1872 {
1873  GstQuery *query;
1874  gboolean res;
1875  nsresult rv;
1876  nsAutoMonitor lock(mMonitor);
1877 
1878  if (!mPipeline)
1879  return NS_ERROR_NOT_AVAILABLE;
1880 
1881  query = gst_query_new_duration(GST_FORMAT_TIME);
1882  res = gst_element_query(mPipeline, query);
1883 
1884  if(res) {
1885  gint64 duration;
1886  gst_query_parse_duration(query, NULL, &duration);
1887 
1888  if ((GstClockTime)duration == GST_CLOCK_TIME_NONE) {
1889  /* Something erroneously returned TRUE for this query
1890  * despite not giving us a duration. Treat the same as
1891  * a failed query */
1892  rv = NS_ERROR_NOT_AVAILABLE;
1893  }
1894  else {
1895  /* Convert to milliseconds */
1896  *aDuration = duration / GST_MSECOND;
1897  rv = NS_OK;
1898  }
1899  }
1900  else
1901  rv = NS_ERROR_NOT_AVAILABLE;
1902 
1903  gst_query_unref (query);
1904 
1905  return rv;
1906 }
1907 
1908 /*virtual*/ nsresult
1910 {
1911  GstQuery *query;
1912  gboolean res;
1913  nsresult rv;
1914  nsAutoMonitor lock(mMonitor);
1915 
1916  if (!mPipeline)
1917  return NS_ERROR_NOT_AVAILABLE;
1918 
1919  query = gst_query_new_position(GST_FORMAT_TIME);
1920  res = gst_element_query(mPipeline, query);
1921 
1922  if(res) {
1923  gint64 position;
1924  gst_query_parse_position(query, NULL, &position);
1925 
1926  if (position == 0 || (GstClockTime)position == GST_CLOCK_TIME_NONE) {
1927  // GStreamer bugs can cause us to get a position of zero when we in fact
1928  // don't know the current position. A real position of zero is unlikely
1929  // and transient, so we just treat this as unknown.
1930  // A value of -1 (GST_CLOCK_TIME_NONE) is also 'not available', though
1931  // really that should be reported as a a false return from the query.
1932  rv = NS_ERROR_NOT_AVAILABLE;
1933  }
1934  else {
1935  /* Convert to milliseconds */
1936  *aPosition = position / GST_MSECOND;
1937  rv = NS_OK;
1938  }
1939  }
1940  else
1941  rv = NS_ERROR_NOT_AVAILABLE;
1942 
1943  gst_query_unref (query);
1944 
1945  return rv;
1946 }
1947 
1948 /*virtual*/ nsresult
1950 {
1951  return Seek(aPosition, sbIMediacorePlaybackControl::SEEK_FLAG_NORMAL);
1952 }
1953 
1954 /*virtual*/ nsresult
1955 sbGStreamerMediacore::OnSeek(PRUint64 aPosition, PRUint32 aFlags)
1956 {
1957  GstClockTime position;
1958  gboolean ret;
1959  GstSeekFlags flags;
1960 
1961  nsAutoMonitor lock(mMonitor);
1962 
1963  // Incoming position is in milliseconds, convert to GstClockTime (nanoseconds)
1964  position = aPosition * GST_MSECOND;
1965 
1966  // For gapless to work after seeking in MP3, we need to do accurate seeking.
1967  // However, accurate seeks on large files is very slow.
1968  // So, we do a KEY_UNIT seek (the fastest sort) unless it's all three of:
1969  // - a local file
1970  // - sufficiently small
1971  // - flag passed is to do a normal seek
1972 
1973  if (mResourceIsLocal &&
1975  aFlags == SEEK_FLAG_NORMAL)
1976  {
1977  flags = (GstSeekFlags)(GST_SEEK_FLAG_FLUSH | GST_SEEK_FLAG_ACCURATE);
1978  }
1979  else {
1980  flags = (GstSeekFlags)(GST_SEEK_FLAG_FLUSH | GST_SEEK_FLAG_KEY_UNIT);
1981  }
1982 
1983  ret = gst_element_seek_simple (mPipeline, GST_FORMAT_TIME,
1984  flags, position);
1985 
1986  if (!ret) {
1987  /* TODO: Is this appropriate for a non-fatal failure to seek? Should we
1988  fire an event? */
1989  return NS_ERROR_FAILURE;
1990  }
1991 
1992 
1993  // Set state to PAUSED. Leave mTargetState as it is, so if we did a seek when
1994  // playing, we'll return to playing (after rebuffering if required)
1995  ret = gst_element_set_state (mPipeline, GST_STATE_PAUSED);
1996 
1997  if (ret == GST_STATE_CHANGE_FAILURE)
1998  return NS_ERROR_FAILURE;
1999 
2000  nsresult rv = SendInitialBufferingEvent();
2001  NS_ENSURE_SUCCESS (rv, rv);
2002 
2003  return rv;
2004 }
2005 
2006 /*virtual*/ nsresult
2008 {
2009  if (mTargetState == GST_STATE_NULL) {
2010  *aIsPlayingAudio = PR_FALSE;
2011  }
2012  else {
2013  *aIsPlayingAudio = mHasAudio;
2014  }
2015 
2016  return NS_OK;
2017 }
2018 
2019 /*virtual*/ nsresult
2021 {
2022  if (mTargetState == GST_STATE_NULL) {
2023  *aIsPlayingVideo = PR_FALSE;
2024  }
2025  else {
2026  *aIsPlayingVideo = mHasVideo;
2027  }
2028 
2029  return NS_OK;
2030 }
2031 
2032 /*virtual*/ nsresult
2034 {
2035  GstStateChangeReturn ret;
2036  GstState curstate;
2037 
2038  nsAutoMonitor lock(mMonitor);
2039  NS_ENSURE_STATE(mPipeline);
2040 
2041  gst_element_get_state (mPipeline, &curstate, NULL, 0);
2042 
2043  mTargetState = GST_STATE_PLAYING;
2044 
2045  if (curstate == GST_STATE_PAUSED && !mBuffering) {
2046  // If we're already paused, then go directly to PLAYING, unless
2047  // we're still waiting for buffering to complete.
2048  ret = gst_element_set_state (mPipeline, GST_STATE_PLAYING);
2049  }
2050  else {
2051  // Otherwise, we change our state to PAUSED (our target state is
2052  // PLAYING, though). Then, when we reach PAUSED, we'll either
2053  // continue on to PLAYING, or (if we're buffering) wait for buffering
2054  // to complete.
2055  ret = gst_element_set_state (mPipeline, GST_STATE_PAUSED);
2056 
2057  nsresult rv = SendInitialBufferingEvent();
2058  NS_ENSURE_SUCCESS (rv, rv);
2059  }
2060 
2061  /* Usually ret will be GST_STATE_CHANGE_ASYNC, but we could get a synchronous
2062  * error - however, in such a case we'll always still receive an error event,
2063  * so we just return OK here, and then fail playback once we process the
2064  * error event. */
2065  if (ret == GST_STATE_CHANGE_FAILURE)
2066  return NS_OK;
2067  else if (ret == GST_STATE_CHANGE_NO_PREROLL)
2068  {
2069  /* NO_PREROLL means we have a live pipeline, for which we have to
2070  * handle buffering differently */
2071  mIsLive = PR_TRUE;
2072  }
2073 
2074  return NS_OK;
2075 }
2076 
2077 nsresult
2079 {
2080  nsAutoMonitor lock(mMonitor);
2081 
2082  // If we're starting an HTTP stream, send an immediate buffering event,
2083  // since GStreamer won't do that until it's connected to the server.
2084  PRBool schemeIsHttp;
2085  nsresult rv = mUri->SchemeIs("http", &schemeIsHttp);
2086  NS_ENSURE_SUCCESS (rv, rv);
2087 
2088  if (schemeIsHttp) {
2089  double bufferingProgress = 0.0;
2090  nsCOMPtr<nsIVariant> variant = sbNewVariant(bufferingProgress).get();
2092  }
2093 
2094  return NS_OK;
2095 }
2096 
2097 /*virtual*/ nsresult
2099 {
2100  GstStateChangeReturn ret;
2101 
2102  nsAutoMonitor lock(mMonitor);
2103 
2104  NS_ENSURE_STATE(mPipeline);
2105 
2106  mTargetState = GST_STATE_PAUSED;
2107  ret = gst_element_set_state (mPipeline, GST_STATE_PAUSED);
2108 
2109  if (ret == GST_STATE_CHANGE_FAILURE)
2110  return NS_ERROR_FAILURE;
2111  return NS_OK;
2112 }
2113 
2114 /*virtual*/ nsresult
2116 {
2117  nsAutoMonitor lock(mMonitor);
2118  mTargetState = GST_STATE_NULL;
2119  mStopped = PR_TRUE;
2120  // If we get stopped without ever starting, that's ok...
2121  if (!mPipeline)
2122  return NS_OK;
2123 
2124  GstElement *pipeline = (GstElement *)g_object_ref (mPipeline);
2125  lock.Exit();
2126 
2127  gst_element_set_state (pipeline, GST_STATE_NULL);
2128  g_object_unref (pipeline);
2129 
2130 
2131  return NS_OK;
2132 }
2133 
2134 //-----------------------------------------------------------------------------
2135 // sbBaseMediacoreVolumeControl
2136 //-----------------------------------------------------------------------------
2137 
2138 /*virtual*/ nsresult
2140 {
2141  mVolume = 1.0;
2142  mMute = PR_FALSE;
2143 
2144  return NS_OK;
2145 }
2146 
2147 /*virtual*/ nsresult
2149 {
2150  nsAutoMonitor lock(mMonitor);
2151 
2152  NS_ENSURE_STATE(mPipeline);
2153 
2154  if(!aMute && mMute) {
2155  nsAutoMonitor mon(sbBaseMediacoreVolumeControl::mMonitor);
2156 
2157  /* Well, this is nice and easy! */
2158  g_object_set(mPipeline, "volume", mVolume, NULL);
2159  }
2160  else if(aMute && !mMute){
2161  /* We have no explicit mute control, so just set the volume to zero, but
2162  * don't update our internal mVolume value */
2163  g_object_set(mPipeline, "volume", 0.0, NULL);
2164  }
2165 
2166  return NS_OK;
2167 }
2168 
2169 /*virtual*/ nsresult
2171 {
2172  nsAutoMonitor lock(mMonitor);
2173 
2174  NS_ENSURE_STATE(mPipeline);
2175 
2176  /* Well, this is nice and easy! */
2177  g_object_set(mPipeline, "volume", aVolume, NULL);
2178 
2179  return NS_OK;
2180 }
2181 
2182 //-----------------------------------------------------------------------------
2183 // sbIMediacoreVotingParticipant
2184 //-----------------------------------------------------------------------------
2185 
2186 NS_IMETHODIMP
2187 sbGStreamerMediacore::VoteWithURI(nsIURI *aURI, PRUint32 *_retval)
2188 {
2189  NS_ENSURE_ARG_POINTER(aURI);
2190  NS_ENSURE_ARG_POINTER(_retval);
2191 
2192  // XXXAus: Run aURI through extension filtering first.
2193  //
2194  // After that, that's as much as we can do, it's most likely
2195  // playable.
2196 
2197  *_retval = 2000;
2198 
2199  return NS_OK;
2200 }
2201 
2202 NS_IMETHODIMP
2203 sbGStreamerMediacore::VoteWithChannel(nsIChannel *aChannel, PRUint32 *_retval)
2204 {
2205  return NS_ERROR_NOT_IMPLEMENTED;
2206 }
2207 
2208 //-----------------------------------------------------------------------------
2209 // sbIMediacoreVideoWindow
2210 //-----------------------------------------------------------------------------
2211 
2212 NS_IMETHODIMP
2213 sbGStreamerMediacore::GetFullscreen(PRBool *aFullscreen)
2214 {
2215  NS_ENSURE_ARG_POINTER(aFullscreen);
2216 
2217  if (mPlatformInterface) {
2218  *aFullscreen = mPlatformInterface->GetFullscreen();
2219  return NS_OK;
2220  }
2221  else {
2222  // Not implemented for this platform
2223  return NS_ERROR_NOT_IMPLEMENTED;
2224  }
2225 }
2226 
2227 NS_IMETHODIMP
2228 sbGStreamerMediacore::SetFullscreen(PRBool aFullscreen)
2229 {
2230  if (mPlatformInterface) {
2231  mPlatformInterface->SetFullscreen(aFullscreen);
2232  return NS_OK;
2233  }
2234  else {
2235  // Not implemented for this platform
2236  return NS_ERROR_NOT_IMPLEMENTED;
2237  }
2238 }
2239 
2240 NS_IMETHODIMP
2241 sbGStreamerMediacore::GetVideoWindow(nsIDOMXULElement **aVideoWindow)
2242 {
2243  nsAutoMonitor mon(mMonitor);
2244  NS_IF_ADDREF(*aVideoWindow = mVideoWindow);
2245 
2246  return NS_OK;
2247 }
2248 
2249 NS_IMETHODIMP
2250 sbGStreamerMediacore::SetVideoWindow(nsIDOMXULElement *aVideoWindow)
2251 {
2252  NS_ENSURE_ARG_POINTER(aVideoWindow);
2253 
2254  nsAutoMonitor mon(mMonitor);
2255 
2256  // Get the box object representing the actual display area for the video.
2257  nsCOMPtr<nsIBoxObject> boxObject;
2258  nsresult rv = aVideoWindow->GetBoxObject(getter_AddRefs(boxObject));
2259  NS_ENSURE_SUCCESS (rv, rv);
2260 
2261  nsCOMPtr<nsIDOMDocument> domDocument;
2262  rv = aVideoWindow->GetOwnerDocument(getter_AddRefs(domDocument));
2263  NS_ENSURE_SUCCESS(rv, rv);
2264 
2265  nsCOMPtr<nsIDOMDocumentView> domDocumentView(do_QueryInterface(domDocument));
2266  NS_ENSURE_TRUE(domDocumentView, NS_NOINTERFACE);
2267 
2268  nsCOMPtr<nsIDOMAbstractView> domAbstractView;
2269  rv = domDocumentView->GetDefaultView(getter_AddRefs(domAbstractView));
2270  NS_ENSURE_SUCCESS(rv, rv);
2271 
2272  nsCOMPtr<nsIWebNavigation> webNavigation(do_GetInterface(domAbstractView));
2273  nsCOMPtr<nsIDocShellTreeItem> docShellTreeItem(do_QueryInterface(webNavigation));
2274  NS_ENSURE_TRUE(docShellTreeItem, NS_NOINTERFACE);
2275 
2276  nsCOMPtr<nsIDocShellTreeOwner> docShellTreeOwner;
2277  rv = docShellTreeItem->GetTreeOwner(getter_AddRefs(docShellTreeOwner));
2278  NS_ENSURE_SUCCESS(rv, rv);
2279 
2280  nsCOMPtr<nsIBaseWindow> baseWindow = do_QueryInterface(docShellTreeOwner);
2281  NS_ENSURE_TRUE(baseWindow, NS_NOINTERFACE);
2282 
2283  nsCOMPtr<nsIWidget> widget;
2284  rv = baseWindow->GetMainWidget(getter_AddRefs(widget));
2285  NS_ENSURE_SUCCESS(rv, rv);
2286 
2287  // Attach event listeners
2288  nsCOMPtr<nsIDocument> document(do_QueryInterface(domDocument));
2289  NS_ENSURE_TRUE(document, NS_NOINTERFACE);
2290 
2291  mDOMWindow = do_QueryInterface(document->GetScriptGlobalObject());
2292  NS_ENSURE_TRUE(mDOMWindow, NS_NOINTERFACE);
2293 
2294  nsCOMPtr<nsIDOMEventTarget> target(do_QueryInterface(mDOMWindow));
2295  NS_ENSURE_TRUE(target, NS_NOINTERFACE);
2296  target->AddEventListener(NS_LITERAL_STRING("resize"), this, PR_FALSE);
2297  target->AddEventListener(NS_LITERAL_STRING("unload"), this, PR_FALSE);
2298  target->AddEventListener(NS_LITERAL_STRING("hide"), this, PR_FALSE);
2299 
2300  NS_WARN_IF_FALSE(NS_IsMainThread(), "Wrong Thread!");
2301 
2302  mVideoWindow = aVideoWindow;
2303 
2304  if (mPlatformInterface) {
2305  rv = mPlatformInterface->SetVideoBox(boxObject, widget);
2306  NS_ENSURE_SUCCESS(rv, rv);
2307 
2308  rv = mPlatformInterface->SetDocument(domDocument);
2309  NS_ENSURE_SUCCESS(rv, rv);
2310  }
2311 
2312  return NS_OK;
2313 }
2314 
2315 //-----------------------------------------------------------------------------
2316 // sbIGStreamerMediacore
2317 //-----------------------------------------------------------------------------
2318 
2319 NS_IMETHODIMP
2320 sbGStreamerMediacore::GetGstreamerVersion(nsAString& aGStreamerVersion)
2321 {
2322  nsString versionString;
2323 
2324  versionString.AppendInt(GST_VERSION_MAJOR);
2325  versionString.AppendLiteral(".");
2326  versionString.AppendInt(GST_VERSION_MINOR);
2327  versionString.AppendLiteral(".");
2328  versionString.AppendInt(GST_VERSION_MICRO);
2329 
2330  aGStreamerVersion.Assign(versionString);
2331 
2332  return NS_OK;
2333 }
2334 
2335 // Forwarding functions for sbIMediacoreEventTarget interface
2336 
2337 NS_IMETHODIMP
2338 sbGStreamerMediacore::DispatchEvent(sbIMediacoreEvent *aEvent,
2339  PRBool aAsync,
2340  PRBool* _retval)
2341 {
2342  return mBaseEventTarget ?
2343  mBaseEventTarget->DispatchEvent(aEvent, aAsync, _retval) :
2344  NS_ERROR_NULL_POINTER;
2345 }
2346 
2347 NS_IMETHODIMP
2348 sbGStreamerMediacore::AddListener(sbIMediacoreEventListener *aListener)
2349 {
2350  return mBaseEventTarget ?
2351  mBaseEventTarget->AddListener(aListener) :
2352  NS_ERROR_NULL_POINTER;
2353 }
2354 
2355 NS_IMETHODIMP
2356 sbGStreamerMediacore::RemoveListener(sbIMediacoreEventListener *aListener)
2357 {
2358  return mBaseEventTarget ?
2359  mBaseEventTarget->RemoveListener(aListener) :
2360  NS_ERROR_NULL_POINTER;
2361 }
2362 
2363 
2364 //-----------------------------------------------------------------------------
2365 // nsIDOMEventListener
2366 //-----------------------------------------------------------------------------
2367 
2368 NS_IMETHODIMP
2369 sbGStreamerMediacore::HandleEvent(nsIDOMEvent* aEvent)
2370 {
2371  nsAutoString eventType;
2372  aEvent->GetType(eventType);
2373 
2374  if(eventType.EqualsLiteral("unload") ||
2375  eventType.EqualsLiteral("hide")) {
2376 
2377  // Clean up here
2378  nsCOMPtr<nsIDOMEventTarget> target(do_QueryInterface(mDOMWindow));
2379  NS_ENSURE_TRUE(target, NS_NOINTERFACE);
2380  target->RemoveEventListener(NS_LITERAL_STRING("resize"), this, PR_FALSE);
2381  target->RemoveEventListener(NS_LITERAL_STRING("unload"), this, PR_FALSE);
2382  target->RemoveEventListener(NS_LITERAL_STRING("hide"), this, PR_FALSE);
2383 
2384  mDOMWindow = nsnull;
2385  mVideoWindow = nsnull;
2386 
2387  if (mPlatformInterface) {
2388  // Clear the video box/widget used by the platform-specific code.
2389  nsresult rv = mPlatformInterface->SetVideoBox(nsnull, nsnull);
2390  NS_ENSURE_SUCCESS(rv, rv);
2391  }
2392  }
2393  else if(eventType.EqualsLiteral("resize") &&
2395  mPlatformInterface->ResizeToWindow();
2396  }
2397 
2398  return NS_OK;
2399 }
2400 
2401 
2402 //-----------------------------------------------------------------------------
2403 // nsIObserver
2404 //-----------------------------------------------------------------------------
2405 
2406 NS_IMETHODIMP
2407 sbGStreamerMediacore::Observe(nsISupports *aSubject,
2408  const char *aTopic,
2409  const PRUnichar *aData)
2410 {
2411  if (!strcmp(NS_PREFBRANCH_PREFCHANGE_TOPIC_ID, aTopic)) {
2412  nsresult rv = ReadPreferences();
2413  NS_ENSURE_SUCCESS(rv, rv);
2414  }
2415 
2416  return NS_OK;
2417 }
2418 
2419 //-----------------------------------------------------------------------------
2420 // sbIGstAudioFilter
2421 //-----------------------------------------------------------------------------
2422 nsresult
2424 {
2425  // Hold a reference to the element
2426  gst_object_ref (aElement);
2427 
2428  mAudioFilters.push_back(aElement);
2429 
2430  return NS_OK;
2431 }
2432 
2433 nsresult
2435 {
2436  mAudioFilters.erase(
2437  std::remove(mAudioFilters.begin(), mAudioFilters.end(), aElement));
2438 
2439  gst_object_unref (aElement);
2440 
2441  return NS_OK;
2442 }
2443 
2444 
2445 
nsCOMPtr< sbIVideoBox > mVideoSize
nsresult InitBaseMediacore()
Songbird Mediacore Error Definition.
virtual nsresult OnSetEqEnabled(PRBool aEqEnabled)
virtual nsresult OnInitBaseMediacoreVolumeControl()
virtual nsresult OnSetVolume(PRFloat64 aVolume)
virtual nsresult OnSetBand(sbIMediacoreEqualizerBand *aBand)
nsAutoPtr< sbBaseMediacoreEventTarget > mBaseEventTarget
#define EQUALIZER_DEFAULT_BAND_COUNT
return NS_OK
nsCOMPtr< sbIPropertyArray > mProperties
void HandleMissingPluginMessage(GstMessage *message)
nsresult GetMediacoreErrorFromGstError(GError *gerror, nsString aResource, GStreamer::pipelineOp_t aPipelineOp, sbIMediacoreError **_retval)
id service()
nsCOMPtr< nsIArray > mProperties
virtual void HandleMessage(GstMessage *message)
nsIVariant * get() const
const unsigned long STREAM_HAS_VIDEO
Stream has video.
virtual nsresult OnInitBaseMediacoreMultibandEqualizer()
virtual nsresult OnShutdown()
virtual nsresult OnSetUri(nsIURI *aURI)
virtual nsresult OnInitBaseMediacorePlaybackControl()
void HandleTagMessage(GstMessage *message)
#define LOG(args)
void HandleStateChangedMessage(GstMessage *message)
nsString Format(const nsAString &aKey, nsTArray< nsString > &aParams, const nsAString &aDefault=SBVoidString())
nsCOMPtr< nsIPrefBranch2 > mPrefs
sbDeviceFirmwareAutoCheckForUpdate prototype flags
std::vector< GstElement * > mAudioFilters
void HandleBufferingMessage(GstMessage *message)
var event
nsCOMPtr< sbIMediacoreError > mMediacoreError
virtual nsresult RemoveAudioFilter(GstElement *aElement)
virtual nsresult OnGetBand(PRUint32 aBandIndex, sbIMediacoreEqualizerBand *aBand)
nsresult SetBufferingProperties(GstElement *aPipeline)
virtual nsresult OnGetDuration(PRUint64 *aDuration)
NS_IMPL_THREADSAFE_CI(sbMediaListEnumeratorWrapper)
GstElement * CreateSinkFromPrefs(const char *aSinkDescription)
function fn remove
const unsigned long ERROR_EVENT
Indicates the event is an error and will have its error member set.
#define EQUALIZER_BANDS
NS_IMPL_CI_INTERFACE_GETTER8(sbLocalDatabaseSmartMediaList, nsIClassInfo, nsISupportsWeakReference, sbILibraryResource, sbILocalDatabaseSmartMediaList, sbIMediaItem, sbIMediaList, sbIMediaListListener, nsIObserver)
const unsigned long METADATA_CHANGE
Metadata describing current item has changed.
void HandleErrorMessage(GstMessage *message)
Songbird Variant Utility Definitions.
var ioService
GstBusSyncReply SyncToAsyncDispatcher(GstBus *bus, GstMessage *message, gpointer data)
const unsigned long STREAM_STOP
Stream was stopped.
virtual PRBool HandleSynchronousMessage(GstMessage *message)
virtual nsresult OnGetIsPlayingVideo(PRBool *aIsPlayingVideo)
const unsigned long SB_STREAM_CODEC_NOT_FOUND
void OnAudioCapsSet(GstCaps *caps)
nsCOMPtr< sbIMediacoreSequencer > mSequencer
NS_IMPL_THREADSAFE_RELEASE(sbRequestItem)
#define SBGSTREAMERSERVICE_CONTRACTID
NS_IMPL_THREADSAFE_ADDREF(sbRequestItem)
virtual nsresult OnInitBaseMediacore()
Definition of the sbIMediacoreEvent interface.
static void Error(char const *aCategory, nsAString const &aMessage, nsAString const &aSource=nsString(), PRUint32 aLine=0)
const unsigned long BUFFERING
Buffering.
virtual nsresult OnSeek(PRUint64 aPosition, PRUint32 aFlag)
#define TRACE(args)
virtual nsresult OnSetMute(PRBool aMute)
const unsigned long STREAM_START
Stream has started.
var bundle
attribute nsIDOMXULElement videoWindow
DOM XUL Element used as the video box.
const sbCreateProxiedComponent do_ProxiedGetService(const nsCID &aCID, nsresult *error=0)
const unsigned long VIDEO_SIZE_CHANGED
Video size has changed.
readonly attribute unsigned long long duration
const nsIChannel
function num(elem, prop)
attribute unsigned long long position
void OnVideoCapsSet(GstCaps *caps)
virtual nsresult OnGetBandCount(PRUint32 *aBandCount)
double SB_ClampDouble(double aGain, double aMin, double aMax)
unique done
#define EQUALIZER_FACTORY_NAME
#define MAX_FILE_SIZE_FOR_ACCURATE_SEEK
virtual nsresult OnGetCapabilities()
GstMessage * message
const unsigned long BUFFER_UNDERRUN
Buffer underrun, operation is likely to halt.
virtual nsresult OnSetPosition(PRUint64 aPosition)
static nsresult CreateEvent(PRUint32 aType, sbIMediacoreError *aError, nsIVariant *aData, sbIMediacore *aOrigin, sbIMediacoreEvent **retval)
void HandleEOSMessage(GstMessage *message)
return ret
Songbird String Bundle Definitions.
const unsigned long STREAM_PAUSE
Stream is now paused.
void HandleRedirectMessage(GstMessage *message)
function url(spec)
virtual nsresult AddAudioFilter(GstElement *aElement)
NS_IMPL_QUERY_INTERFACE11_CI(sbGStreamerMediacore, sbIMediacore, sbIMediacoreMultibandEqualizer, sbIMediacorePlaybackControl, sbIMediacoreVolumeControl, sbIMediacoreVotingParticipant, sbIMediacoreEventTarget, sbIMediacoreVideoWindow, sbIGStreamerMediacore, nsIDOMEventListener, nsIObserver, nsIClassInfo) NS_IMPL_CI_INTERFACE_GETTER8(sbGStreamerMediacore
void HandleWarningMessage(GstMessage *message)
sbIJobCancelable NS_DECL_CLASSINFO(sbGstreamerMediaInspector)
virtual nsresult OnGetIsPlayingAudio(PRBool *aIsPlayingAudio)
void DispatchMediacoreEvent(unsigned long type, nsIVariant *aData=NULL, sbIMediacoreError *aError=NULL)
nsresult ConvertTagListToPropertyArray(GstTagList *taglist, sbIPropertyArray **aPropertyArray)
nsresult GetFileSize(nsIURI *aURI, PRInt64 *aFileSize)
attribute sbIMediacoreSequencer sequencer
#define SB_MEDIACOREMANAGER_CONTRACTID
#define SB_PROPERTY_TRACKNAME
const unsigned long URI_CHANGE
URI used for operation has changed.
#define SB_PROPERTY_CONTENTURL
const NS_PREFBRANCH_PREFCHANGE_TOPIC_ID
observe data
Definition: FeedWriter.js:1329
const unsigned long STREAM_END
End of stream.
_getSelectedPageStyle s i
Array filter(tab.attributes, function(aAttr){return(_this.xulAttributes.indexOf(aAttr.name) >-1);}).forEach(tab.removeAttribute
bool SetPropertyOnChild(GstElement *aElement, const char *aPropertyName, gint64 aPropertyValue)
nsIDOMEventListener
nsCOMPtr< nsIDOMXULElement > mVideoWindow
nsAutoPtr< sbIGstPlatformInterface > mPlatformInterface
_updateTextAndScrollDataForFrame aData
virtual nsresult OnGetPosition(PRUint64 *aPosition)
var file
nsCOMPtr< nsIDOMWindow > mDOMWindow