Backed out changeset 5db9f8fbcaf4 (bug 883731) for webplatform 3 test failures
authorCarsten "Tomcat" Book <cbook@mozilla.com>
Mon, 29 Sep 2014 10:57:48 +0200
changeset 207673 1f9df75e4ee865179c9ced78985dff3e0fdea41c
parent 207672 ff03c5e417b4a811905b5de41b68f59adf305238
child 207674 b5c3cd762cf2c30cd40823d48cf85d34c42235e5
push id27564
push userryanvm@gmail.com
push dateMon, 29 Sep 2014 18:57:04 +0000
treeherdermozilla-central@ce9a0b34225e [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
bugs883731
milestone35.0a1
backs out5db9f8fbcaf47a67d468a8d5ac9e58c4e07af4e2
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
Backed out changeset 5db9f8fbcaf4 (bug 883731) for webplatform 3 test failures
content/html/content/public/HTMLMediaElement.h
content/html/content/src/HTMLMediaElement.cpp
content/media/MediaCache.cpp
content/media/MediaDecoder.cpp
content/media/MediaDecoder.h
content/media/MediaDecoderOwner.h
content/media/MediaResource.cpp
--- a/content/html/content/public/HTMLMediaElement.h
+++ b/content/html/content/public/HTMLMediaElement.h
@@ -157,19 +157,25 @@ public:
   virtual 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(const MediaInfo* aInfo,
                               const MetadataTags* aTags) MOZ_FINAL MOZ_OVERRIDE;
 
-  // Called by the decoder object, on the main thread,
-  // when it has read the first frame of the video or audio.
-  virtual void FirstFrameLoaded() 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;
+
+  // Called by the video decoder object, on the main thread,
+  // when the resource has completed downloading.
+  virtual void ResourceLoaded() MOZ_FINAL MOZ_OVERRIDE;
 
   // Called by the video decoder object, on the main thread,
   // when the resource has a network error during loading.
   virtual void NetworkError() MOZ_FINAL MOZ_OVERRIDE;
 
   // Called by the video decoder object, on the main thread, when the
   // resource has a decode error during metadata loading or decoding.
   virtual void DecodeError() MOZ_FINAL MOZ_OVERRIDE;
@@ -227,16 +233,20 @@ public:
 
   // Called by the decoder when some data has been downloaded or
   // buffering/seeking has ended. aNextFrameAvailable is true when
   // the data for the next frame is available. This method will
   // decide whether to set the ready state to HAVE_CURRENT_DATA,
   // HAVE_FUTURE_DATA or HAVE_ENOUGH_DATA.
   virtual void UpdateReadyStateForData(MediaDecoderOwner::NextFrameStatus aNextFrame) MOZ_FINAL MOZ_OVERRIDE;
 
+  // Use this method to change the mReadyState member, so required
+  // events can be fired.
+  void ChangeReadyState(nsMediaReadyState aState);
+
   // Return true if we can activate autoplay assuming enough data has arrived.
   bool CanActivateAutoplay();
 
   // Notify that state has changed that might cause an autoplay element to
   // start playing.
   // If the element is 'autoplay' and is ready to play back (not paused,
   // autoplay pref enabled, etc), it should start playing back.
   void CheckAutoplayDataReady();
@@ -525,17 +535,17 @@ public:
 
   // XPCOM MozPreservesPitch() is OK
 
 #ifdef MOZ_EME
   MediaKeys* GetMediaKeys() const;
 
   already_AddRefed<Promise> SetMediaKeys(MediaKeys* mediaKeys,
                                          ErrorResult& aRv);
-
+  
   MediaWaitingFor WaitingFor() const;
 
   mozilla::dom::EventHandlerNonNull* GetOnencrypted();
   void SetOnencrypted(mozilla::dom::EventHandlerNonNull* listener);
 
   void DispatchEncrypted(const nsTArray<uint8_t>& aInitData,
                          const nsAString& aInitDataType);
 
@@ -633,21 +643,16 @@ protected:
     void UpdateWakeLock();
 
     bool mValue;
     bool mCanPlay;
     HTMLMediaElement* mOuter;
     nsCOMPtr<nsITimer> mTimer;
   };
 
-  /** Use this method to change the mReadyState member, so required
-   * events can be fired.
-   */
-  void ChangeReadyState(nsMediaReadyState aState);
-
   /**
    * These two methods are called by the WakeLockBoolWrapper when the wakelock
    * has to be created or released.
    */
   virtual void WakeLockCreate();
   virtual void WakeLockRelease();
   nsRefPtr<WakeLock> mWakeLock;
 
@@ -923,17 +928,17 @@ protected:
   // This method does the check for muting/fading/unmuting the audio channel.
   nsresult UpdateChannelMuteState(mozilla::dom::AudioChannelState aCanPlay);
 
   // Seeks to aTime seconds. aSeekType can be Exact to seek to exactly the
   // seek target, or PrevSyncPoint if a quicker but less precise seek is
   // desired, and we'll seek to the sync point (keyframe and/or start of the
   // next block of audio samples) preceeding seek target.
   void Seek(double aTime, SeekTarget::Type aSeekType, ErrorResult& aRv);
-
+  
   // Update the audio channel playing state
   void UpdateAudioChannelPlayingState();
 
   // Adds to the element's list of pending text tracks each text track
   // in the element's list of text tracks whose text track mode is not disabled
   // and whose text track readiness state is loading.
   void PopulatePendingTextTrackList();
 
@@ -1094,18 +1099,19 @@ protected:
 
   // Stores the time at the start of the current 'played' range.
   double mCurrentPlayRangeStart;
 
   // If true then we have begun downloading the media content.
   // Set to false when completed, or not yet started.
   bool mBegun;
 
-  // True if loadeddata has been fired.
-  bool mLoadedDataFired;
+  // True when the decoder has loaded enough data to display the
+  // first frame of the content.
+  bool mLoadedFirstFrame;
 
   // Indicates whether current playback is a result of user action
   // (ie. calling of the Play method), or automatic playback due to
   // the 'autoplay' attribute being set. A true value indicates the
   // latter case.
   // The 'autoplay' HTML attribute indicates that the video should
   // start playing when loaded. The 'autoplay' attribute of the object
   // is a mirror of the HTML attribute. These are different from this
--- a/content/html/content/src/HTMLMediaElement.cpp
+++ b/content/html/content/src/HTMLMediaElement.cpp
@@ -656,18 +656,17 @@ void HTMLMediaElement::AbortExistingLoad
 
   if (mNetworkState == nsIDOMHTMLMediaElement::NETWORK_LOADING ||
       mNetworkState == nsIDOMHTMLMediaElement::NETWORK_IDLE)
   {
     DispatchAsyncEvent(NS_LITERAL_STRING("abort"));
   }
 
   mError = nullptr;
-  mBegun = false;
-  mLoadedDataFired = false;
+  mLoadedFirstFrame = false;
   mAutoplaying = true;
   mIsLoadingFromSourceChildren = false;
   mSuspendedAfterFirstFrame = false;
   mAllowSuspendAfterFirstFrame = true;
   mHaveQueuedSelectResource = false;
   mSuspendedForPreloadNone = false;
   mDownloadSuspendedByCache = false;
   mSourcePointer = nullptr;
@@ -2023,17 +2022,17 @@ HTMLMediaElement::HTMLMediaElement(alrea
     mFragmentStart(-1.0),
     mFragmentEnd(-1.0),
     mDefaultPlaybackRate(1.0),
     mPlaybackRate(1.0),
     mPreservesPitch(true),
     mPlayed(new TimeRanges),
     mCurrentPlayRangeStart(-1.0),
     mBegun(false),
-    mLoadedDataFired(false),
+    mLoadedFirstFrame(false),
     mAutoplaying(true),
     mAutoplayEnabled(true),
     mPaused(true),
     mMuted(0),
     mStatsShowing(false),
     mAllowCasting(false),
     mIsCasting(false),
     mAudioCaptured(false),
@@ -2772,17 +2771,17 @@ public:
       mElement->FireTimeUpdate(true);
     }
   }
   void DoNotifyHaveCurrentData()
   {
     mHaveCurrentData = true;
     if (mElement) {
       nsRefPtr<HTMLMediaElement> deathGrip = mElement;
-      mElement->FirstFrameLoaded();
+      mElement->FirstFrameLoaded(false);
     }
     UpdateReadyStateForData();
     DoNotifyOutput();
   }
 
   // These notifications run on the media graph thread so we need to
   // dispatch events to the main thread.
   virtual void NotifyBlockingChanged(MediaStreamGraph* aGraph, Blocking aBlocked) MOZ_OVERRIDE
@@ -2866,17 +2865,18 @@ void HTMLMediaElement::SetupSrcMediaStre
   mSrcStream->ConstructMediaTracks(AudioTracks(), VideoTracks());
 
   ChangeReadyState(nsIDOMHTMLMediaElement::HAVE_METADATA);
   DispatchAsyncEvent(NS_LITERAL_STRING("durationchange"));
   DispatchAsyncEvent(NS_LITERAL_STRING("loadedmetadata"));
   DispatchAsyncEvent(NS_LITERAL_STRING("suspend"));
   mNetworkState = nsIDOMHTMLMediaElement::NETWORK_IDLE;
   AddRemoveSelfReference();
-  // FirstFrameLoaded() will be called when the stream has current data.
+  // FirstFrameLoaded(false) will be called when the stream has current data,
+  // to complete the setup by entering the HAVE_CURRENT_DATA state.
 }
 
 void HTMLMediaElement::EndSrcMediaStreamPlayback()
 {
   MediaStream* stream = GetSrcMediaStream();
   if (stream) {
     stream->RemoveListener(mSrcStreamListener);
   }
@@ -2916,17 +2916,16 @@ void HTMLMediaElement::ProcessMediaFragm
   }
 }
 
 void HTMLMediaElement::MetadataLoaded(const MediaInfo* aInfo,
                                       const MetadataTags* aTags)
 {
   mHasAudio = aInfo->HasAudio();
   mTags = aTags;
-  mLoadedDataFired = false;
   ChangeReadyState(nsIDOMHTMLMediaElement::HAVE_METADATA);
   DispatchAsyncEvent(NS_LITERAL_STRING("durationchange"));
   DispatchAsyncEvent(NS_LITERAL_STRING("loadedmetadata"));
   if (mDecoder && mDecoder->IsTransportSeekable() && mDecoder->IsMediaSeekable()) {
     ProcessMediaFragmentURI();
     mDecoder->SetFragmentEndTime(mFragmentEnd);
   }
 
@@ -2936,42 +2935,68 @@ void HTMLMediaElement::MetadataLoaded(co
   if (!aInfo->HasVideo() && mVideoFrameContainer) {
     // call ForgetElement() such that callbacks from |mVideoFrameContainer|
     // won't reach us anymore.
     mVideoFrameContainer->ForgetElement();
     mVideoFrameContainer = nullptr;
   }
 }
 
-void HTMLMediaElement::FirstFrameLoaded()
+void HTMLMediaElement::FirstFrameLoaded(bool aResourceFullyLoaded)
 {
+  ChangeReadyState(aResourceFullyLoaded ?
+    nsIDOMHTMLMediaElement::HAVE_ENOUGH_DATA :
+    nsIDOMHTMLMediaElement::HAVE_CURRENT_DATA);
+  ChangeDelayLoadStatus(false);
+
   NS_ASSERTION(!mSuspendedAfterFirstFrame, "Should not have already suspended");
 
-  ChangeDelayLoadStatus(false);
-
   if (mDecoder && mAllowSuspendAfterFirstFrame && mPaused &&
+      !aResourceFullyLoaded &&
       !HasAttr(kNameSpaceID_None, nsGkAtoms::autoplay) &&
       mPreloadAction == HTMLMediaElement::PRELOAD_METADATA) {
     mSuspendedAfterFirstFrame = true;
     mDecoder->Suspend();
-  } else if (mDownloadSuspendedByCache &&
-             mDecoder && !mDecoder->IsEnded()) {
+  } else if (mLoadedFirstFrame &&
+             mDownloadSuspendedByCache &&
+             mDecoder &&
+             !mDecoder->IsEnded()) {
     // We've already loaded the first frame, and the decoder has signalled
     // that the download has been suspended by the media cache. So move
     // readyState into HAVE_ENOUGH_DATA, in case there's script waiting
     // for a "canplaythrough" event; without this forced transition, we will
     // never fire the "canplaythrough" event if the media cache is so small
     // that the download was suspended before the first frame was loaded.
     // Don't force this transition if the decoder is in ended state; the
     // readyState should remain at HAVE_CURRENT_DATA in this case.
     ChangeReadyState(nsIDOMHTMLMediaElement::HAVE_ENOUGH_DATA);
     return;
   }
 }
 
+void HTMLMediaElement::ResourceLoaded()
+{
+  NS_ASSERTION(!mSrcStream, "Don't call this for streams");
+
+  mBegun = false;
+  mNetworkState = nsIDOMHTMLMediaElement::NETWORK_IDLE;
+  AddRemoveSelfReference();
+  if (mReadyState >= nsIDOMHTMLMediaElement::HAVE_METADATA) {
+    // MediaStream sources are put into HAVE_CURRENT_DATA state here on setup. If the
+    // stream is not blocked, we will receive a notification that will put it
+    // into HAVE_ENOUGH_DATA state.
+    ChangeReadyState(mSrcStream ? nsIDOMHTMLMediaElement::HAVE_CURRENT_DATA
+                     : nsIDOMHTMLMediaElement::HAVE_ENOUGH_DATA);
+  }
+  // Ensure a progress event is dispatched at the end of download.
+  DispatchAsyncEvent(NS_LITERAL_STRING("progress"));
+  // The download has stopped.
+  DispatchAsyncEvent(NS_LITERAL_STRING("suspend"));
+}
+
 void HTMLMediaElement::NetworkError()
 {
   Error(nsIDOMMediaError::MEDIA_ERR_NETWORK);
 }
 
 void HTMLMediaElement::DecodeError()
 {
   nsAutoString src;
@@ -3116,39 +3141,41 @@ bool HTMLMediaElement::ShouldCheckAllowO
 {
   return mCORSMode != CORS_NONE;
 }
 
 void HTMLMediaElement::UpdateReadyStateForData(MediaDecoderOwner::NextFrameStatus aNextFrame)
 {
   if (mReadyState < nsIDOMHTMLMediaElement::HAVE_METADATA) {
     // aNextFrame might have a next frame because the decoder can advance
-    // on its own thread before MetadataLoaded gets a chance to run.
+    // on its own thread before ResourceLoaded or MetadataLoaded gets
+    // a chance to run.
     // The arrival of more data can't change us out of this readyState.
     return;
   }
 
   // Section 2.4.3.1 of the Media Source Extensions spec requires
   // changing to HAVE_METADATA when seeking into an unbuffered
   // range.
   if (aNextFrame == MediaDecoderOwner::NEXT_FRAME_WAIT_FOR_MSE_DATA) {
     ChangeReadyState(nsIDOMHTMLMediaElement::HAVE_METADATA);
     return;
   }
 
-  if (mDownloadSuspendedByCache && mDecoder && !mDecoder->IsEnded()) {
-    // The decoder has signaled that the download has been suspended by the
+  if (mReadyState > nsIDOMHTMLMediaElement::HAVE_METADATA &&
+      mDownloadSuspendedByCache &&
+      mDecoder &&
+      !mDecoder->IsEnded()) {
+    // The decoder has signalled that the download has been suspended by the
     // media cache. So move readyState into HAVE_ENOUGH_DATA, in case there's
     // script waiting for a "canplaythrough" event; without this forced
     // transition, we will never fire the "canplaythrough" event if the
     // media cache is too small, and scripts are bound to fail. Don't force
     // this transition if the decoder is in ended state; the readyState
     // should remain at HAVE_CURRENT_DATA in this case.
-    // Note that this state transition includes the case where we finished
-    // downloaded the whole data stream.
     ChangeReadyState(nsIDOMHTMLMediaElement::HAVE_ENOUGH_DATA);
     return;
   }
 
   if (aNextFrame != MediaDecoderOwner::NEXT_FRAME_AVAILABLE) {
     ChangeReadyState(nsIDOMHTMLMediaElement::HAVE_CURRENT_DATA);
     if (!mWaitingFired && aNextFrame == MediaDecoderOwner::NEXT_FRAME_UNAVAILABLE_BUFFERING) {
       FireTimeUpdate(false);
@@ -3209,19 +3236,20 @@ void HTMLMediaElement::ChangeReadyState(
   // Handle raising of "waiting" event during seek (see 4.8.10.9)
   if (mPlayingBeforeSeek &&
       oldState < nsIDOMHTMLMediaElement::HAVE_FUTURE_DATA) {
     DispatchAsyncEvent(NS_LITERAL_STRING("waiting"));
   }
 
   if (oldState < nsIDOMHTMLMediaElement::HAVE_CURRENT_DATA &&
       mReadyState >= nsIDOMHTMLMediaElement::HAVE_CURRENT_DATA &&
-      !mLoadedDataFired) {
+      !mLoadedFirstFrame)
+  {
     DispatchAsyncEvent(NS_LITERAL_STRING("loadeddata"));
-    mLoadedDataFired = true;
+    mLoadedFirstFrame = true;
   }
 
   if (mReadyState == nsIDOMHTMLMediaElement::HAVE_CURRENT_DATA) {
     mWaitingFired = false;
   }
 
   if (oldState < nsIDOMHTMLMediaElement::HAVE_FUTURE_DATA &&
       mReadyState >= nsIDOMHTMLMediaElement::HAVE_FUTURE_DATA) {
--- a/content/media/MediaCache.cpp
+++ b/content/media/MediaCache.cpp
@@ -1852,34 +1852,33 @@ MediaCacheStream::NotifyDataEnded(nsresu
 
   if (NS_FAILED(aStatus)) {
     // Disconnect from other streams sharing our resource, since they
     // should continue trying to load. Our load might have been deliberately
     // canceled and that shouldn't affect other streams.
     mResourceID = gMediaCache->AllocateResourceID();
   }
 
-  // It is prudent to update channel/cache status before calling
-  // CacheClientNotifyDataEnded() which will read |mChannelEnded|.
   FlushPartialBlockInternal(true);
-  mChannelEnded = true;
-  gMediaCache->QueueUpdate();
 
   MediaCache::ResourceStreamIterator iter(mResourceID);
   while (MediaCacheStream* stream = iter.Next()) {
     if (NS_SUCCEEDED(aStatus)) {
       // We read the whole stream, so remember the true length
       stream->mStreamLength = mChannelOffset;
     }
     if (!stream->mDidNotifyDataEnded) {
       stream->mDidNotifyDataEnded = true;
       stream->mNotifyDataEndedStatus = aStatus;
       stream->mClient->CacheClientNotifyDataEnded(aStatus);
     }
   }
+
+  mChannelEnded = true;
+  gMediaCache->QueueUpdate();
 }
 
 void
 MediaCacheStream::NotifyChannelRecreated()
 {
   NS_ASSERTION(NS_IsMainThread(), "Only call on main thread");
   ReentrantMonitorAutoEnter mon(gMediaCache->GetReentrantMonitor());
   mChannelEnded = false;
--- a/content/media/MediaDecoder.cpp
+++ b/content/media/MediaDecoder.cpp
@@ -435,16 +435,17 @@ MediaDecoder::MediaDecoder() :
   mDuration(-1),
   mMediaSeekable(true),
   mSameOriginMedia(false),
   mReentrantMonitor("media.decoder"),
   mIsDormant(false),
   mIsExitingDormant(false),
   mPlayState(PLAY_STATE_PAUSED),
   mNextState(PLAY_STATE_PAUSED),
+  mCalledResourceLoaded(false),
   mIgnoreProgressData(false),
   mInfiniteStream(false),
   mOwner(nullptr),
   mPlaybackStatistics(new MediaChannelStatistics()),
   mPinnedForSeek(false),
   mShuttingDown(false),
   mPausedForPlaybackRateNull(false),
   mMinimizePreroll(false),
@@ -718,18 +719,30 @@ void MediaDecoder::MetadataLoaded(MediaI
 
   if (mOwner) {
     // Make sure the element and the frame (if any) are told about
     // our new size.
     Invalidate();
     mOwner->MetadataLoaded(aInfo, aTags);
   }
 
+  if (!mCalledResourceLoaded) {
+    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"));
+  }
+
+  // Only inform the element of FirstFrameLoaded if not doing a load() in order
+  // to fulfill a seek, otherwise we'll get multiple loadedfirstframe events.
+  bool notifyResourceIsLoaded = !mCalledResourceLoaded &&
+                                IsDataCachedToEndOfResource();
   if (mOwner) {
-    mOwner->FirstFrameLoaded();
+    mOwner->FirstFrameLoaded(notifyResourceIsLoaded);
   }
 
   // This can run cache callbacks.
   mResource->EnsureCacheUpToDate();
 
   // The element can run javascript via events
   // before reaching here, so only change the
   // state if we're still set to the original
@@ -738,21 +751,55 @@ void MediaDecoder::MetadataLoaded(MediaI
     if (mRequestedSeekTarget.IsValid()) {
       ChangeState(PLAY_STATE_SEEKING);
     }
     else {
       ChangeState(mNextState);
     }
   }
 
+  if (notifyResourceIsLoaded) {
+    ResourceLoaded();
+  }
+
   // Run NotifySuspendedStatusChanged now to give us a chance to notice
   // that autoplay should run.
   NotifySuspendedStatusChanged();
 }
 
+void MediaDecoder::ResourceLoaded()
+{
+  MOZ_ASSERT(NS_IsMainThread());
+
+  // Don't handle ResourceLoaded if we are shutting down, or if
+  // we need to ignore progress data due to seeking (in the case
+  // that the seek results in reaching end of file, we get a bogus call
+  // to ResourceLoaded).
+  if (mShuttingDown)
+    return;
+
+  {
+    // If we are seeking or loading then the resource loaded notification we get
+    // should be ignored, since it represents the end of the seek request.
+    ReentrantMonitorAutoEnter mon(GetReentrantMonitor());
+    if (mIgnoreProgressData || mCalledResourceLoaded || mPlayState == PLAY_STATE_LOADING)
+      return;
+
+    Progress(false);
+
+    mCalledResourceLoaded = true;
+    StopProgress();
+  }
+
+  // Ensure the final progress event gets fired
+  if (mOwner) {
+    mOwner->ResourceLoaded();
+  }
+}
+
 void MediaDecoder::ResetConnectionState()
 {
   MOZ_ASSERT(NS_IsMainThread());
   if (mShuttingDown)
     return;
 
   if (mOwner) {
     // Notify the media element that connection gets lost.
@@ -977,22 +1024,22 @@ void MediaDecoder::NotifyDownloadEnded(n
   }
 
   {
     ReentrantMonitorAutoEnter mon(GetReentrantMonitor());
     UpdatePlaybackRate();
   }
 
   if (NS_SUCCEEDED(aStatus)) {
-    UpdateReadyStateForData();
-    // A final progress event will be fired by the MediaResource calling
-    // DownloadSuspended on the element.
-  } else if (aStatus != NS_BASE_STREAM_CLOSED) {
+    ResourceLoaded();
+  }
+  else if (aStatus != NS_BASE_STREAM_CLOSED) {
     NetworkError();
   }
+  UpdateReadyStateForData();
 }
 
 void MediaDecoder::NotifyPrincipalChanged()
 {
   if (mOwner) {
     mOwner->NotifyDecoderPrincipalChanged();
   }
 }
--- a/content/media/MediaDecoder.h
+++ b/content/media/MediaDecoder.h
@@ -303,16 +303,19 @@ public:
   // point of the first frame of data.
   // This is called at most once per decoder, after Init().
   virtual nsresult Load(nsIStreamListener** aListener,
                         MediaDecoder* aCloneDonor);
 
   // Called in |Load| to open mResource.
   nsresult OpenResource(nsIStreamListener** aStreamListener);
 
+  // Called when the video file has completed downloading.
+  virtual void ResourceLoaded();
+
   // Called if the media file encounters a network error.
   virtual void NetworkError();
 
   // Get the current MediaResource being used. Its URI will be returned
   // by currentSrc. Returns what was passed to Load(), if Load() has been called.
   // Note: The MediaResource is refcounted, but it outlives the MediaDecoder,
   // so it's OK to use the reference returned by this function without
   // refcounting, *unless* you need to store and use the reference after the
@@ -1151,16 +1154,21 @@ protected:
   // decode thread.
   // This can only be changed on the main thread while holding the decoder
   // monitor. Thus, it can be safely read while holding the decoder monitor
   // OR on the main thread.
   // If the SeekTarget's IsValid() accessor returns false, then no seek has
   // been requested. When a seek is started this is reset to invalid.
   SeekTarget mRequestedSeekTarget;
 
+  // True when we have fully loaded the resource and reported that
+  // to the element (i.e. reached NETWORK_LOADED state).
+  // Accessed on the main thread only.
+  bool mCalledResourceLoaded;
+
   // True when seeking or otherwise moving the play position around in
   // such a manner that progress event data is inaccurate. This is set
   // during seek and duration operations to prevent the progress indicator
   // from jumping around. Read/Write from any thread. Must have decode monitor
   // locked before accessing.
   bool mIgnoreProgressData;
 
   // True if the stream is infinite (e.g. a webradio).
--- a/content/media/MediaDecoderOwner.h
+++ b/content/media/MediaDecoderOwner.h
@@ -47,19 +47,25 @@ public:
   virtual bool GetPaused() = 0;
 
   // Called by the video decoder object, on the main thread,
   // when it has read the metadata containing video dimensions,
   // etc.
   virtual void MetadataLoaded(const MediaInfo* aInfo,
                               const MetadataTags* aTags) = 0;
 
-  // Called by the decoder object, on the main thread,
-  // when it has read the first frame of the video or audio.
-  virtual void FirstFrameLoaded() = 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;
+
+  // Called by the video decoder object, on the main thread,
+  // when the resource has completed downloading.
+  virtual void ResourceLoaded() = 0;
 
   // Called by the video decoder object, on the main thread,
   // when the resource has a network error during loading.
   virtual void NetworkError() = 0;
 
   // Called by the video decoder object, on the main thread, when the
   // resource has a decode error during metadata loading or decoding.
   virtual void DecodeError() = 0;
--- a/content/media/MediaResource.cpp
+++ b/content/media/MediaResource.cpp
@@ -979,34 +979,21 @@ ChannelMediaResource::CacheClientNotifyD
 }
 
 class DataEnded : public nsRunnable {
 public:
   DataEnded(MediaDecoder* aDecoder, nsresult aStatus) :
     mDecoder(aDecoder), mStatus(aStatus) {}
   NS_IMETHOD Run() {
     mDecoder->NotifyDownloadEnded(mStatus);
-    if (NS_SUCCEEDED(mStatus)) {
-      MediaDecoderOwner* owner = mDecoder->GetMediaOwner();
-      if (owner) {
-        dom::HTMLMediaElement* element = owner->GetMediaElement();
-        if (element) {
-          element->DownloadSuspended();
-        }
-      }
-      // NotifySuspendedStatusChanged will tell the element that download
-      // has been suspended "by the cache", which is true since we never download
-      // anything. The element can then transition to HAVE_ENOUGH_DATA.
-      mDecoder->NotifySuspendedStatusChanged();
-    }
     return NS_OK;
   }
 private:
   nsRefPtr<MediaDecoder> mDecoder;
-  nsresult               mStatus;
+  nsresult                 mStatus;
 };
 
 void
 ChannelMediaResource::CacheClientNotifyDataEnded(nsresult aStatus)
 {
   MOZ_ASSERT(NS_IsMainThread());
   // NOTE: this can be called with the media cache lock held, so don't
   // block or do anything which might try to acquire a lock!
@@ -1256,18 +1243,18 @@ public:
   }
   virtual int64_t GetCachedDataEnd(int64_t aOffset) {
     MutexAutoLock lock(mLock);
 
     EnsureSizeInitialized();
     return std::max(aOffset, mSize);
   }
   virtual bool    IsDataCachedToEndOfResource(int64_t aOffset) { return true; }
-  virtual bool    IsSuspendedByCache() { return true; }
-  virtual bool    IsSuspended() { return true; }
+  virtual bool    IsSuspendedByCache() { return false; }
+  virtual bool    IsSuspended() { return false; }
   virtual bool    IsTransportSeekable() MOZ_OVERRIDE { return true; }
 
   nsresult GetCachedRanges(nsTArray<MediaByteRange>& aRanges);
 
   virtual size_t SizeOfExcludingThis(
                         MallocSizeOf aMallocSizeOf) const MOZ_OVERRIDE
   {
     // Might be useful to track in the future: