Bug 804875 - Reset media element when loading a new src. r=kinetik
authorPaul Adenot <paul@paul.cx>
Tue, 15 Jan 2013 19:57:03 +0100
changeset 118912 f14639a3461e362013e51bfc07b3e94c09736074
parent 118911 23456fc210523338914d9395a443d771b60b3fba
child 118913 55e93d1fa9724afd3cfd806fd4d357574748fbac
push id21354
push userpaul@paul.cx
push dateTue, 15 Jan 2013 19:02:36 +0000
treeherdermozilla-inbound@b3a8618f901c [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerskinetik
bugs804875
milestone21.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 804875 - Reset media element when loading a new src. r=kinetik
content/html/content/public/nsHTMLMediaElement.h
content/html/content/src/nsHTMLAudioElement.cpp
content/html/content/src/nsHTMLMediaElement.cpp
content/media/AbstractMediaDecoder.h
content/media/MediaDecoder.cpp
content/media/MediaDecoder.h
content/media/MediaDecoderOwner.h
content/media/MediaDecoderStateMachine.cpp
content/media/MediaDecoderStateMachine.h
content/media/MediaMetadataManager.h
content/media/VideoFrameContainer.cpp
content/media/VideoFrameContainer.h
content/media/ogg/OggReader.cpp
gfx/layers/ImageContainer.h
--- a/content/html/content/public/nsHTMLMediaElement.h
+++ b/content/html/content/public/nsHTMLMediaElement.h
@@ -115,16 +115,17 @@ public:
   void NotifyOwnerDocumentActivityChanged();
 
   // Called by the video decoder object, on the main thread,
   // when it has read the metadata containing video dimensions,
   // etc.
   virtual void MetadataLoaded(int aChannels,
                               int aRate,
                               bool aHasAudio,
+                              bool aHasVideo,
                               const MetadataTags* aTags) MOZ_FINAL MOZ_OVERRIDE;
 
   // 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.
   virtual void FirstFrameLoaded(bool aResourceFullyLoaded) MOZ_FINAL MOZ_OVERRIDE;
 
@@ -471,16 +472,22 @@ protected:
 
   /**
    * Asynchronously awaits a stable state, and then causes SelectResource()
    * to be run on the main thread's event loop.
    */
   void QueueSelectResourceTask();
 
   /**
+   * When loading a new source on an existing media element, make sure to reset
+   * everything that is accessible using the media element API.
+   */
+  void ResetState();
+
+  /**
    * The resource-fetch algorithm step of the load algorithm.
    */
   nsresult LoadResource();
 
   /**
    * Selects the next <source> child from which to load a resource. Called
    * during the resource selection algorithm. Stores the return value in
    * mSourceLoadCandidate before returning.
--- a/content/html/content/src/nsHTMLAudioElement.cpp
+++ b/content/html/content/src/nsHTMLAudioElement.cpp
@@ -118,17 +118,17 @@ nsHTMLAudioElement::MozSetup(uint32_t aC
   mAudioStream = AudioStream::AllocateStream();
   nsresult rv = mAudioStream->Init(aChannels, aRate, mAudioChannelType);
   if (NS_FAILED(rv)) {
     mAudioStream->Shutdown();
     mAudioStream = nullptr;
     return rv;
   }
 
-  MetadataLoaded(aChannels, aRate, true, nullptr);
+  MetadataLoaded(aChannels, aRate, true, false, nullptr);
   mAudioStream->SetVolume(mVolume);
 
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsHTMLAudioElement::MozWriteAudio(const JS::Value& aData, JSContext* aCx, uint32_t* aRetVal)
 {
--- a/content/html/content/src/nsHTMLMediaElement.cpp
+++ b/content/html/content/src/nsHTMLMediaElement.cpp
@@ -730,20 +730,30 @@ NS_IMETHODIMP nsHTMLMediaElement::Load()
 {
   if (mIsRunningLoadMethod)
     return NS_OK;
   SetPlayedOrSeeked(false);
   mIsRunningLoadMethod = true;
   AbortExistingLoads();
   SetPlaybackRate(mDefaultPlaybackRate);
   QueueSelectResourceTask();
+  ResetState();
   mIsRunningLoadMethod = false;
   return NS_OK;
 }
 
+void nsHTMLMediaElement::ResetState()
+{
+  mMediaSize = nsIntSize(-1, -1);
+  VideoFrameContainer* container = GetVideoFrameContainer();
+  if (container) {
+    container->Reset();
+  }
+}
+
 static bool HasSourceChildren(nsIContent *aElement)
 {
   for (nsIContent* child = aElement->GetFirstChild();
        child;
        child = child->GetNextSibling()) {
     if (child->IsHTML(nsGkAtoms::source))
     {
       return true;
@@ -2593,29 +2603,37 @@ void nsHTMLMediaElement::ProcessMediaFra
     SetCurrentTime(start);
     mFragmentStart = start;
   }
 }
 
 void nsHTMLMediaElement::MetadataLoaded(int aChannels,
                                         int aRate,
                                         bool aHasAudio,
+                                        bool aHasVideo,
                                         const MetadataTags* aTags)
 {
   mChannels = aChannels;
   mRate = aRate;
   mHasAudio = aHasAudio;
   mTags = aTags;
   ChangeReadyState(nsIDOMHTMLMediaElement::HAVE_METADATA);
   DispatchAsyncEvent(NS_LITERAL_STRING("durationchange"));
   DispatchAsyncEvent(NS_LITERAL_STRING("loadedmetadata"));
   if (mDecoder && mDecoder->IsTransportSeekable() && mDecoder->IsMediaSeekable()) {
     ProcessMediaFragmentURI();
     mDecoder->SetFragmentEndTime(mFragmentEnd);
   }
+
+  // If this element had a video track, but consists only of an audio track now,
+  // delete the VideoFrameContainer. This happens when the src is changed to an
+  // audio only file.
+  if (!aHasVideo) {
+    mVideoFrameContainer = nullptr;
+  }
 }
 
 void nsHTMLMediaElement::FirstFrameLoaded(bool aResourceFullyLoaded)
 {
   ChangeReadyState(aResourceFullyLoaded ?
     nsIDOMHTMLMediaElement::HAVE_ENOUGH_DATA :
     nsIDOMHTMLMediaElement::HAVE_CURRENT_DATA);
   ChangeDelayLoadStatus(false);
@@ -2949,16 +2967,24 @@ void nsHTMLMediaElement::NotifyAutoplayD
       GetSrcMediaStream()->ChangeExplicitBlockerCount(-1);
     }
     DispatchAsyncEvent(NS_LITERAL_STRING("play"));
   }
 }
 
 VideoFrameContainer* nsHTMLMediaElement::GetVideoFrameContainer()
 {
+  // If we have loaded the metadata, and the size of the video is still
+  // (-1, -1), the media has no video. Don't go a create a video frame
+  // container.
+  if (mReadyState >= nsIDOMHTMLMediaElement::HAVE_METADATA &&
+      mMediaSize == nsIntSize(-1, -1)) {
+    return nullptr;
+  }
+
   if (mVideoFrameContainer)
     return mVideoFrameContainer;
 
   // If we have a print surface, this is just a static image so
   // no image container is required
   if (mPrintSurface)
     return nullptr;
 
--- a/content/media/AbstractMediaDecoder.h
+++ b/content/media/AbstractMediaDecoder.h
@@ -76,18 +76,18 @@ public:
   virtual mozilla::layers::ImageContainer* GetImageContainer() = 0;
 
   // Return true if the media layer supports seeking.
   virtual bool IsTransportSeekable() = 0;
 
   // Return true if the transport layer supports seeking.
   virtual bool IsMediaSeekable() = 0;
 
-  virtual void MetadataLoaded(int aChannels, int aRate, bool aHasAudio, MetadataTags* aTags) = 0;
-  virtual void QueueMetadata(int64_t aTime, int aChannels, int aRate, bool aHasAudio, MetadataTags* aTags) = 0;
+  virtual void MetadataLoaded(int aChannels, int aRate, bool aHasAudio, bool aHasVideo, MetadataTags* aTags) = 0;
+  virtual void QueueMetadata(int64_t aTime, int aChannels, int aRate, bool aHasAudio, bool aHasVideo, MetadataTags* aTags) = 0;
 
   // Set the media end time in microseconds
   virtual void SetMediaEndTime(int64_t aTime) = 0;
 
   // Make the decoder state machine update the playback position. Called by
   // the reader on the decoder thread (Assertions for this checked by
   // mDecoderStateMachine). This must be called with the decode monitor
   // held.
@@ -114,33 +114,35 @@ public:
   };
 };
 
 class AudioMetadataEventRunner : public nsRunnable
 {
   private:
     nsRefPtr<AbstractMediaDecoder> mDecoder;
   public:
-    AudioMetadataEventRunner(AbstractMediaDecoder* aDecoder, int aChannels, int aRate, bool aHasAudio, MetadataTags* aTags)
+    AudioMetadataEventRunner(AbstractMediaDecoder* aDecoder, int aChannels, int aRate, bool aHasAudio, bool aHasVideo, MetadataTags* aTags)
       : mDecoder(aDecoder),
         mChannels(aChannels),
         mRate(aRate),
         mHasAudio(aHasAudio),
+        mHasVideo(aHasVideo),
         mTags(aTags)
   {}
 
   NS_IMETHOD Run()
   {
-    mDecoder->MetadataLoaded(mChannels, mRate, mHasAudio, mTags);
+    mDecoder->MetadataLoaded(mChannels, mRate, mHasAudio, mHasVideo, mTags);
     return NS_OK;
   }
 
   int mChannels;
   int mRate;
   bool mHasAudio;
+  bool mHasVideo;
   MetadataTags* mTags;
 };
 
 
 }
 
 #endif
 
--- a/content/media/MediaDecoder.cpp
+++ b/content/media/MediaDecoder.cpp
@@ -642,25 +642,26 @@ void MediaDecoder::AudioAvailable(float*
   }
   mOwner->NotifyAudioAvailable(frameBuffer.forget(), aFrameBufferLength, aTime);
 }
 
 void MediaDecoder::QueueMetadata(int64_t aPublishTime,
                                  int aChannels,
                                  int aRate,
                                  bool aHasAudio,
+                                 bool aHasVideo,
                                  MetadataTags* aTags)
 {
   NS_ASSERTION(mDecoderStateMachine->OnDecodeThread(),
                "Should be on decode thread.");
   GetReentrantMonitor().AssertCurrentThreadIn();
-  mDecoderStateMachine->QueueMetadata(aPublishTime, aChannels, aRate, aHasAudio, aTags);
+  mDecoderStateMachine->QueueMetadata(aPublishTime, aChannels, aRate, aHasAudio, aHasVideo, aTags);
 }
 
-void MediaDecoder::MetadataLoaded(int aChannels, int aRate, bool aHasAudio, MetadataTags* aTags)
+void MediaDecoder::MetadataLoaded(int aChannels, int aRate, bool aHasAudio, bool aHasVideo, MetadataTags* aTags)
 {
   MOZ_ASSERT(NS_IsMainThread());
   if (mShuttingDown) {
     return;
   }
 
   {
     ReentrantMonitorAutoEnter mon(GetReentrantMonitor());
@@ -672,17 +673,17 @@ void MediaDecoder::MetadataLoaded(int aC
   if (mDuration == -1) {
     SetInfinite(true);
   }
 
   if (mOwner) {
     // Make sure the element and the frame (if any) are told about
     // our new size.
     Invalidate();
-    mOwner->MetadataLoaded(aChannels, aRate, aHasAudio, aTags);
+    mOwner->MetadataLoaded(aChannels, aRate, aHasAudio, aHasVideo, aTags);
   }
 
   if (!mResourceLoaded) {
     StartProgress();
   } else if (mOwner) {
     // Resource was loaded during metadata loading, when progress
     // events are being ignored. Fire the final progress event.
     mOwner->DispatchAsyncEvent(NS_LITERAL_STRING("progress"));
--- a/content/media/MediaDecoder.h
+++ b/content/media/MediaDecoder.h
@@ -658,16 +658,17 @@ public:
 
   // Send a new set of metadata to the state machine, to be dispatched to the
   // main thread to be presented when the |currentTime| of the media is greater
   // or equal to aPublishTime.
   void QueueMetadata(int64_t aPublishTime,
                      int aChannels,
                      int aRate,
                      bool aHasAudio,
+                     bool aHasVideo,
                      MetadataTags* aTags);
 
   /******
    * The following methods must only be called on the main
    * thread.
    ******/
 
   // Change to a new play state. This updates the mState variable and
@@ -676,17 +677,17 @@ public:
   void ChangeState(PlayState aState);
 
   // May be called by the reader to notify this decoder that the metadata from
   // the media file has been read. Call on the decode thread only.
   void OnReadMetadataCompleted() MOZ_OVERRIDE { }
 
   // Called when the metadata from the media file has been loaded by the
   // state machine. Call on the main thread only.
-  void MetadataLoaded(int aChannels, int aRate, bool aHasAudio, MetadataTags* aTags);
+  void MetadataLoaded(int aChannels, int aRate, bool aHasAudio, bool aHasVideo, MetadataTags* aTags);
 
   // 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/MediaDecoderOwner.h
+++ b/content/media/MediaDecoderOwner.h
@@ -51,16 +51,17 @@ public:
                                     float aTime) = 0;
 
   // Called by the video decoder object, on the main thread,
   // when it has read the metadata containing video dimensions,
   // etc.
   virtual void MetadataLoaded(int aChannels,
                               int aRate,
                               bool aHasAudio,
+                              bool aHasVideo,
                               const MetadataTags* aTags) = 0;
 
   // 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.
   virtual void FirstFrameLoaded(bool aResourceFullyLoaded) = 0;
 
--- a/content/media/MediaDecoderStateMachine.cpp
+++ b/content/media/MediaDecoderStateMachine.cpp
@@ -1835,16 +1835,17 @@ nsresult MediaDecoderStateMachine::Decod
     mDecoder->RequestFrameBufferLength(frameBufferLength);
   }
 
   nsCOMPtr<nsIRunnable> metadataLoadedEvent =
     new AudioMetadataEventRunner(mDecoder,
                                  mInfo.mAudioChannels,
                                  mInfo.mAudioRate,
                                  HasAudio(),
+                                 HasVideo(),
                                  tags);
   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();
   }
 
@@ -2745,17 +2746,22 @@ void MediaDecoderStateMachine::SetPreser
 }
 
 bool MediaDecoderStateMachine::IsShutdown()
 {
   mDecoder->GetReentrantMonitor().AssertCurrentThreadIn();
   return GetState() == DECODER_STATE_SHUTDOWN;
 }
 
-void MediaDecoderStateMachine::QueueMetadata(int64_t aPublishTime, int aChannels, int aRate, bool aHasAudio, MetadataTags* aTags)
+void MediaDecoderStateMachine::QueueMetadata(int64_t aPublishTime,
+                                             int aChannels,
+                                             int aRate,
+                                             bool aHasAudio,
+                                             bool aHasVideo,
+                                             MetadataTags* aTags)
 {
   NS_ASSERTION(OnDecodeThread(), "Should be on decode thread.");
   mDecoder->GetReentrantMonitor().AssertCurrentThreadIn();
   TimedMetadata* metadata = new TimedMetadata;
   metadata->mPublishTime = aPublishTime;
   metadata->mChannels = aChannels;
   metadata->mRate = aRate;
   metadata->mHasAudio = aHasAudio;
--- a/content/media/MediaDecoderStateMachine.h
+++ b/content/media/MediaDecoderStateMachine.h
@@ -314,17 +314,17 @@ public:
   void FinishStreamData();
   bool HaveEnoughDecodedAudio(int64_t aAmpleAudioUSecs);
   bool HaveEnoughDecodedVideo();
 
   // Returns true if the state machine has shutdown or is in the process of
   // shutting down. The decoder monitor must be held while calling this.
   bool IsShutdown();
 
-  void QueueMetadata(int64_t aPublishTime, int aChannels, int aRate, bool aHasAudio, MetadataTags* aTags);
+  void QueueMetadata(int64_t aPublishTime, int aChannels, int aRate, bool aHasAudio, bool aHasVideo, MetadataTags* aTags);
 
 protected:
   virtual uint32_t GetAmpleVideoFrames() { return mAmpleVideoFrames; }
 
 private:
   class WakeDecoderRunnable : public nsRunnable {
   public:
     WakeDecoderRunnable(MediaDecoderStateMachine* aSM)
--- a/content/media/MediaMetadataManager.h
+++ b/content/media/MediaMetadataManager.h
@@ -23,16 +23,18 @@ namespace mozilla {
       // the main threads.
       nsAutoPtr<MetadataTags> mTags;
       // The sample rate of this media.
       int mRate;
       // The number of channel of this media.
       int mChannels;
       // True if this media has an audio track.
       bool mHasAudio;
+      // True if this media has a video track.
+      bool mHasVideo;
   };
 
   // This class encapsulate the logic to give the metadata from the reader to
   // the content, at the right time.
   class MediaMetadataManager
   {
     public:
       ~MediaMetadataManager() {
@@ -48,16 +50,17 @@ namespace mozilla {
       void DispatchMetadataIfNeeded(AbstractMediaDecoder* aDecoder, double aCurrentTime) {
         TimedMetadata* metadata = mMetadataQueue.getFirst();
         while (metadata && aCurrentTime >= static_cast<double>(metadata->mPublishTime) / USECS_PER_S) {
           nsCOMPtr<nsIRunnable> metadataUpdatedEvent =
             new mozilla::AudioMetadataEventRunner(aDecoder,
                                                   metadata->mChannels,
                                                   metadata->mRate,
                                                   metadata->mHasAudio,
+                                                  metadata->mHasVideo,
                                                   metadata->mTags.forget());
           NS_DispatchToMainThread(metadataUpdatedEvent, NS_DISPATCH_NORMAL);
           mMetadataQueue.popFirst();
           metadata = mMetadataQueue.getFirst();
         }
       }
     protected:
       LinkedList<TimedMetadata> mMetadataQueue;
--- a/content/media/VideoFrameContainer.cpp
+++ b/content/media/VideoFrameContainer.cpp
@@ -61,38 +61,46 @@ void VideoFrameContainer::SetCurrentFram
   gfxIntSize newFrameSize = mImageContainer->GetCurrentSize();
   if (oldFrameSize != newFrameSize) {
     mImageSizeChanged = true;
   }
 
   mPaintTarget = aTargetTime;
 }
 
-void VideoFrameContainer::ClearCurrentFrame()
+void VideoFrameContainer::Reset()
+{
+  ClearCurrentFrame(true);
+  Invalidate();
+  mPaintDelay = TimeDuration();
+  mImageContainer->ResetPaintCount();
+}
+
+void VideoFrameContainer::ClearCurrentFrame(bool aResetSize)
 {
   MutexAutoLock lock(mMutex);
 
   // See comment in SetCurrentFrame for the reasoning behind
   // using a kungFuDeathGrip here.
   nsRefPtr<Image> kungFuDeathGrip;
   kungFuDeathGrip = mImageContainer->LockCurrentImage();
   mImageContainer->UnlockCurrentImage();
 
   mImageContainer->SetCurrentImage(nullptr);
+  mImageSizeChanged = aResetSize;
 
   // We removed the current image so we will have to invalidate once
   // again to setup the ImageContainer <-> Compositor pair.
   mNeedInvalidation = true;
 }
 
 ImageContainer* VideoFrameContainer::GetImageContainer() {
   return mImageContainer;
 }
 
-
 double VideoFrameContainer::GetFrameDelay()
 {
   MutexAutoLock lock(mMutex);
   return mPaintDelay.ToSeconds();
 }
 
 void VideoFrameContainer::Invalidate()
 {
--- a/content/media/VideoFrameContainer.h
+++ b/content/media/VideoFrameContainer.h
@@ -41,17 +41,19 @@ public:
 
   VideoFrameContainer(nsHTMLMediaElement* aElement,
                       already_AddRefed<ImageContainer> aContainer);
   ~VideoFrameContainer();
 
   // Call on any thread
   void SetCurrentFrame(const gfxIntSize& aIntrinsicSize, Image* aImage,
                        TimeStamp aTargetTime);
-  void ClearCurrentFrame();
+  void ClearCurrentFrame(bool aResetSize = false);
+  // Reset the VideoFrameContainer
+  void Reset();
   // Time in seconds by which the last painted video frame was late by.
   // E.g. if the last painted frame should have been painted at time t,
   // but was actually painted at t+n, this returns n in seconds. Threadsafe.
   double GetFrameDelay();
   // Call on main thread
   void Invalidate();
   ImageContainer* GetImageContainer();
   void ForgetElement() { mElement = nullptr; }
--- a/content/media/ogg/OggReader.cpp
+++ b/content/media/ogg/OggReader.cpp
@@ -707,16 +707,17 @@ bool OggReader::ReadOggChain()
   if (chained) {
     SetChained(true);
     {
       ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor());
       mDecoder->QueueMetadata((mDecodedAudioFrames * USECS_PER_S) / rate,
                                channels,
                                rate,
                                HasAudio(),
+                               HasVideo(),
                                tags);
     }
     return true;
   }
 
   return false;
 }
 
--- a/gfx/layers/ImageContainer.h
+++ b/gfx/layers/ImageContainer.h
@@ -439,16 +439,25 @@ public:
    * and painted at least once.  Can be called from any thread.
    */
   uint32_t GetPaintCount() {
     ReentrantMonitorAutoEnter mon(mReentrantMonitor);
     return mPaintCount;
   }
 
   /**
+   * Resets the paint count to zero.
+   * Can be called from any thread.
+   */
+  void ResetPaintCount() {
+    ReentrantMonitorAutoEnter mon(mReentrantMonitor);
+    mPaintCount = 0;
+  }
+
+  /**
    * Increments mPaintCount if this is the first time aPainted has been
    * painted, and sets mPaintTime if the painted image is the current image.
    * current image.  Can be called from any thread.
    */
   void NotifyPaintedImage(Image* aPainted) {
     ReentrantMonitorAutoEnter mon(mReentrantMonitor);
 
     nsRefPtr<Image> current = mActiveImage;