Bug 480376 - Implement mozHasAudio to indicate when there's no audio track available. r=cpearce
authorPaul ADENOT <paul@paul.cx>
Sat, 28 Apr 2012 11:01:10 -0400
changeset 94931 b579fa03e84a0f29b997376deac3f5718581a78d
parent 94930 f2c2a457f9cc39fb9840001df597d474983738b9
child 94932 89cea655477a01765ddaf95eb5744a8d3f7b1c68
push idunknown
push userunknown
push dateunknown
reviewerscpearce
bugs480376
milestone15.0a1
Bug 480376 - Implement mozHasAudio to indicate when there's no audio track available. r=cpearce
content/html/content/public/nsHTMLMediaElement.h
content/html/content/src/nsHTMLAudioElement.cpp
content/html/content/src/nsHTMLMediaElement.cpp
content/html/content/src/nsHTMLVideoElement.cpp
content/media/nsBuiltinDecoder.cpp
content/media/nsBuiltinDecoder.h
content/media/nsBuiltinDecoderStateMachine.cpp
content/media/test/Makefile.in
content/media/test/manifest.js
content/media/test/test_mozHasAudio.html
dom/interfaces/html/nsIDOMHTMLVideoElement.idl
--- a/content/html/content/public/nsHTMLMediaElement.h
+++ b/content/html/content/public/nsHTMLMediaElement.h
@@ -127,17 +127,17 @@ public:
    * Call this to reevaluate whether we should start/stop due to our owner
    * document being active or inactive.
    */
   void NotifyOwnerDocumentActivityChanged();
 
   // Called by the video decoder object, on the main thread,
   // when it has read the metadata containing video dimensions,
   // etc.
-  void MetadataLoaded(PRUint32 aChannels, PRUint32 aRate);
+  void MetadataLoaded(PRUint32 aChannels, PRUint32 aRate, bool aHasAudio);
 
   // Called by the video decoder object, on the main thread,
   // when it has read the first frame of the video
   // aResourceFullyLoaded should be true if the resource has been
   // fully loaded and the caller will call ResourceLoaded next.
   void FirstFrameLoaded(bool aResourceFullyLoaded);
 
   // Called by the video decoder object, on the main thread,
@@ -789,11 +789,14 @@ protected:
   // stored in mPreloadURI.
   bool mLoadIsSuspended;
 
   // True if a same-origin check has been done for the media element and resource.
   bool mMediaSecurityVerified;
 
   // The CORS mode when loading the media element
   mozilla::CORSMode mCORSMode;
+
+  // True if the media has an audio track
+  bool mHasAudio;
 };
 
 #endif
--- a/content/html/content/src/nsHTMLAudioElement.cpp
+++ b/content/html/content/src/nsHTMLAudioElement.cpp
@@ -172,17 +172,17 @@ nsHTMLAudioElement::MozSetup(PRUint32 aC
   nsresult rv = mAudioStream->Init(aChannels, aRate,
                                    nsAudioStream::FORMAT_FLOAT32);
   if (NS_FAILED(rv)) {
     mAudioStream->Shutdown();
     mAudioStream = nsnull;
     return rv;
   }
 
-  MetadataLoaded(aChannels, aRate);
+  MetadataLoaded(aChannels, aRate, true);
   mAudioStream->SetVolume(mVolume);
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsHTMLAudioElement::MozWriteAudio(const JS::Value& aData, JSContext* aCx, PRUint32* aRetVal)
 {
   if (!mAudioStream) {
--- a/content/html/content/src/nsHTMLMediaElement.cpp
+++ b/content/html/content/src/nsHTMLMediaElement.cpp
@@ -1454,17 +1454,18 @@ nsHTMLMediaElement::nsHTMLMediaElement(a
     mHaveQueuedSelectResource(false),
     mSuspendedAfterFirstFrame(false),
     mAllowSuspendAfterFirstFrame(true),
     mHasPlayedOrSeeked(false),
     mHasSelfReference(false),
     mShuttingDown(false),
     mLoadIsSuspended(false),
     mMediaSecurityVerified(false),
-    mCORSMode(CORS_NONE)
+    mCORSMode(CORS_NONE),
+    mHasAudio(false)
 {
 #ifdef PR_LOGGING
   if (!gMediaElementLog) {
     gMediaElementLog = PR_NewLogModule("nsMediaElement");
   }
   if (!gMediaElementEventsLog) {
     gMediaElementEventsLog = PR_NewLogModule("nsMediaElementEvents");
   }
@@ -2253,20 +2254,21 @@ void nsHTMLMediaElement::ProcessMediaFra
     }
   }
   if (start > 0.0) {
     SetCurrentTime(start);
     mFragmentStart = start;
   }
 }
 
-void nsHTMLMediaElement::MetadataLoaded(PRUint32 aChannels, PRUint32 aRate)
+void nsHTMLMediaElement::MetadataLoaded(PRUint32 aChannels, PRUint32 aRate, bool aHasAudio)
 {
   mChannels = aChannels;
   mRate = aRate;
+  mHasAudio = aHasAudio;
   ChangeReadyState(nsIDOMHTMLMediaElement::HAVE_METADATA);
   DispatchAsyncEvent(NS_LITERAL_STRING("durationchange"));
   DispatchAsyncEvent(NS_LITERAL_STRING("loadedmetadata"));
   if (mDecoder && mDecoder->IsSeekable()) {
     ProcessMediaFragmentURI();
     mDecoder->SetEndTime(mFragmentEnd);
   }
 }
--- a/content/html/content/src/nsHTMLVideoElement.cpp
+++ b/content/html/content/src/nsHTMLVideoElement.cpp
@@ -214,8 +214,16 @@ NS_IMETHODIMP nsHTMLVideoElement::GetMoz
 }
 
 NS_IMETHODIMP nsHTMLVideoElement::GetMozFrameDelay(double *aMozFrameDelay) {
   NS_ASSERTION(NS_IsMainThread(), "Should be on main thread.");
   VideoFrameContainer* container = GetVideoFrameContainer();
   *aMozFrameDelay = container ?  container->GetFrameDelay() : 0;
   return NS_OK;
 }
+
+
+/* readonly attribute bool mozHasAudio */
+NS_IMETHODIMP nsHTMLVideoElement::GetMozHasAudio(bool *aHasAudio) {
+  NS_ASSERTION(NS_IsMainThread(), "Should be on main thread.");
+  *aHasAudio = mHasAudio;
+  return NS_OK;
+}
--- a/content/media/nsBuiltinDecoder.cpp
+++ b/content/media/nsBuiltinDecoder.cpp
@@ -403,17 +403,18 @@ void nsBuiltinDecoder::AudioAvailable(fl
   NS_ASSERTION(NS_IsMainThread(), "Should be on main thread.");
   if (mShuttingDown || !mElement) {
     return;
   }
   mElement->NotifyAudioAvailable(frameBuffer.forget(), aFrameBufferLength, aTime);
 }
 
 void nsBuiltinDecoder::MetadataLoaded(PRUint32 aChannels,
-                                      PRUint32 aRate)
+                                      PRUint32 aRate,
+                                      bool aHasAudio)
 {
   NS_ASSERTION(NS_IsMainThread(), "Should be on main thread.");
   if (mShuttingDown) {
     return;
   }
 
   // Only inform the element of MetadataLoaded if not doing a load() in order
   // to fulfill a seek, otherwise we'll get multiple metadataloaded events.
@@ -430,17 +431,17 @@ void nsBuiltinDecoder::MetadataLoaded(PR
   if (mDuration == -1) {
     SetInfinite(true);
   }
 
   if (mElement && notifyElement) {
     // Make sure the element and the frame (if any) are told about
     // our new size.
     Invalidate();
-    mElement->MetadataLoaded(aChannels, aRate);
+    mElement->MetadataLoaded(aChannels, aRate, aHasAudio);
   }
 
   if (!mResourceLoaded) {
     StartProgress();
   } else if (mElement) {
     // Resource was loaded during metadata loading, when progress
     // events are being ignored. Fire the final progress event.
     mElement->DispatchAsyncEvent(NS_LITERAL_STRING("progress"));
--- a/content/media/nsBuiltinDecoder.h
+++ b/content/media/nsBuiltinDecoder.h
@@ -550,17 +550,18 @@ public:
   // Change to a new play state. This updates the mState variable and
   // notifies any thread blocking on this object's monitor of the
   // change. Call on the main thread only.
   void ChangeState(PlayState aState);
 
   // Called when the metadata from the media file has been read.
   // Call on the main thread only.
   void MetadataLoaded(PRUint32 aChannels,
-                      PRUint32 aRate);
+                      PRUint32 aRate,
+                      bool aHasAudio);
 
   // Called when the first frame has been loaded.
   // Call on the main thread only.
   void FirstFrameLoaded();
 
   // Called when the video has completed playing.
   // Call on the main thread only.
   void PlaybackEnded();
--- a/content/media/nsBuiltinDecoderStateMachine.cpp
+++ b/content/media/nsBuiltinDecoderStateMachine.cpp
@@ -147,31 +147,33 @@ static PRInt64 DurationToUsecs(TimeDurat
 }
 
 class nsAudioMetadataEventRunner : public nsRunnable
 {
 private:
   nsCOMPtr<nsBuiltinDecoder> mDecoder;
 public:
   nsAudioMetadataEventRunner(nsBuiltinDecoder* aDecoder, PRUint32 aChannels,
-                             PRUint32 aRate) :
+                             PRUint32 aRate, bool aHasAudio) :
     mDecoder(aDecoder),
     mChannels(aChannels),
-    mRate(aRate)
+    mRate(aRate),
+    mHasAudio(aHasAudio)
   {
   }
 
   NS_IMETHOD Run()
   {
-    mDecoder->MetadataLoaded(mChannels, mRate);
+    mDecoder->MetadataLoaded(mChannels, mRate, mHasAudio);
     return NS_OK;
   }
 
   const PRUint32 mChannels;
   const PRUint32 mRate;
+  const bool mHasAudio;
 };
 
 // Owns the global state machine thread and counts of
 // state machine and decoder threads. There should
 // only be one instance of this class.
 class StateMachineTracker
 {
 private:
@@ -1510,17 +1512,17 @@ nsresult nsBuiltinDecoderStateMachine::D
     mEventManager.Init(mInfo.mAudioChannels, mInfo.mAudioRate);
     // Set the buffer length at the decoder level to be able, to be able
     // to retrive the value via media element method. The RequestFrameBufferLength
     // will call the nsBuiltinDecoderStateMachine::SetFrameBufferLength().
     PRUint32 frameBufferLength = mInfo.mAudioChannels * FRAMEBUFFER_LENGTH_PER_CHANNEL;
     mDecoder->RequestFrameBufferLength(frameBufferLength);
   }
   nsCOMPtr<nsIRunnable> metadataLoadedEvent =
-    new nsAudioMetadataEventRunner(mDecoder, mInfo.mAudioChannels, mInfo.mAudioRate);
+    new nsAudioMetadataEventRunner(mDecoder, mInfo.mAudioChannels, mInfo.mAudioRate, HasAudio());
   NS_DispatchToMainThread(metadataLoadedEvent, NS_DISPATCH_NORMAL);
 
   if (mState == DECODER_STATE_DECODING_METADATA) {
     LOG(PR_LOG_DEBUG, ("%p Changed state from DECODING_METADATA to DECODING", mDecoder.get()));
     StartDecoding();
   }
 
   if ((mState == DECODER_STATE_DECODING || mState == DECODER_STATE_COMPLETED) &&
--- a/content/media/test/Makefile.in
+++ b/content/media/test/Makefile.in
@@ -151,16 +151,17 @@ include $(topsrcdir)/config/rules.mk
 		test_source_write.html \
 		test_standalone.html \
 		test_timeupdate_small_files.html \
 		test_too_many_elements.html \
 		test_volume.html \
 		test_video_to_canvas.html \
 		use_large_cache.js \
 		test_audiowrite.html \
+		test_mozHasAudio.html \
 		$(NULL)
 
 # Don't run in suite
 ifndef MOZ_SUITE
 _TEST_FILES += test_play_twice.html
 else
 $(warning test_play_twice.html is disabled pending investigation. Bug 598252)
 endif
--- a/content/media/test/manifest.js
+++ b/content/media/test/manifest.js
@@ -42,16 +42,25 @@ var gReplayTests = gSmallTests.concat([
 
 // Used by test_paused_after_ended. Need one test file per decoder backend, plus
 // anything for testing bugs that occur when replying a played file.
 var gPausedAfterEndedTests = gSmallTests.concat([
   { name:"r11025_u8_c1.wav", type:"audio/x-wav", duration:1.0 },
   { name:"small-shot.ogg", type:"video/ogg", duration:0.276 }
 ]);
 
+// Test the mozHasAudio property
+var gMozHasAudioTests = [
+  { name:"big.wav", type:"audio/x-wav", duration:9.278981, size:102444, hasAudio:undefined },
+  { name:"320x240.ogv", type:"video/ogg", width:320, height:240, duration:0.233, size:28942, hasAudio:false },
+  { name:"short-video.ogv", type:"video/ogg", duration:1.081, hasAudio:true },
+  { name:"seek.webm", type:"video/webm", duration:3.966, size:215529, hasAudio:false },
+  { name:"bogus.duh", type:"bogus/duh" }
+];
+
 // These are files that we want to make sure we can play through.  We can
 // also check metadata.  Put files of the same type together in this list so if
 // something crashes we have some idea of which backend is responsible.
 // Used by test_playback, which expects no error event and one ended event.
 var gPlayTests = [
   // 8-bit samples
   { name:"r11025_u8_c1.wav", type:"audio/x-wav", duration:1.0 },
   // 8-bit samples, file is truncated
new file mode 100644
--- /dev/null
+++ b/content/media/test/test_mozHasAudio.html
@@ -0,0 +1,41 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+  <title>Test playback of media files that should play OK</title>
+  <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+  <script type="text/javascript" src="manifest.js"></script>
+</head>
+<body>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+var manager = new MediaTestManager;
+
+function onloadedmetadata(e) {
+  var t = e.target;
+  is(t.mozHasAudio, t.hasAudio, "The element reports the wrong audio property." + t.token);
+  manager.finished(t.token);
+}
+
+function startTest(test, token) {
+  var elemType = /^audio/.test(test.type) ? "audio" : "video";
+  var element = document.createElement(elemType);
+
+  element.token = token;
+  manager.started(token);
+
+  element.src = test.name;
+  element.name = test.name;
+  element.hasAudio = test.hasAudio;
+  element.addEventListener("loadedmetadata", onloadedmetadata, false);
+
+  element.load();
+}
+
+manager.runTests(gMozHasAudioTests, startTest);
+
+</script>
+</pre>
+</body>
+</html>
--- a/dom/interfaces/html/nsIDOMHTMLVideoElement.idl
+++ b/dom/interfaces/html/nsIDOMHTMLVideoElement.idl
@@ -43,17 +43,17 @@
  * <video> element.
  *
  * For more information on this interface, please see
  * http://www.whatwg.org/specs/web-apps/current-work/#video
  *
  * @status UNDER_DEVELOPMENT
  */
 
-[scriptable, uuid(E3763903-928B-47C4-AC3B-C47EB43884CA)]
+[scriptable, uuid(0a1c8b44-12a4-4316-b39f-feeee48475f8)]
 interface nsIDOMHTMLVideoElement : nsIDOMHTMLMediaElement
 {
            attribute long width; 
            attribute long height;
   readonly attribute unsigned long videoWidth;
   readonly attribute unsigned long videoHeight;
            attribute DOMString poster;
            
@@ -70,10 +70,13 @@ interface nsIDOMHTMLVideoElement : nsIDO
   // pipeline. We may drop frames if they arrive late at the renderer.
   readonly attribute unsigned long mozPresentedFrames;
 
   // Number of presented frames which were painted on screen.
   readonly attribute unsigned long mozPaintedFrames;
 
   // Time which the last painted video frame was late by, in seconds.
   readonly attribute double mozFrameDelay;
+
+  // True if the video has an audio track available.
+  readonly attribute bool mozHasAudio;
 };