Backed out changeset 44f27b3c35d4 (bug 1065827)
authorCarsten "Tomcat" Book <cbook@mozilla.com>
Thu, 06 Nov 2014 14:59:04 +0100
changeset 214400 7d7c5b4a91ff89b8a7acbb522c0f05d0de7fe309
parent 214399 52f3406b70240caa1782ca91b29fbec9d71b9ecd
child 214401 6791ee4cd303ed125ef789a7b4bbb3b973040b92
push id27780
push userkwierso@gmail.com
push dateFri, 07 Nov 2014 02:25:05 +0000
treeherdermozilla-central@e6d47abb6a7b [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
bugs1065827
milestone36.0a1
backs out44f27b3c35d47fe906b0ecf42c55de5d4cf0d56e
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 44f27b3c35d4 (bug 1065827)
dom/html/HTMLMediaElement.cpp
dom/media/AbstractMediaDecoder.h
dom/media/MediaDecoder.cpp
dom/media/MediaDecoder.h
dom/media/MediaDecoderReader.h
dom/media/MediaDecoderStateMachine.cpp
dom/media/MediaDecoderStateMachine.h
dom/media/MediaMetadataManager.h
dom/media/fmp4/MP4Reader.cpp
dom/media/fmp4/MP4Reader.h
dom/media/mediasource/SourceBufferDecoder.cpp
dom/media/mediasource/SourceBufferDecoder.h
dom/media/omx/MediaOmxCommonDecoder.cpp
dom/media/omx/MediaOmxCommonDecoder.h
dom/media/webaudio/BufferDecoder.cpp
dom/media/webaudio/BufferDecoder.h
--- a/dom/html/HTMLMediaElement.cpp
+++ b/dom/html/HTMLMediaElement.cpp
@@ -2898,21 +2898,21 @@ void HTMLMediaElement::MetadataLoaded(co
   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.
-  // Else update its dimensions.
-  if (!aInfo->HasVideo()) {
-    ResetState();
-  } else {
-    UpdateMediaSize(aInfo->mVideo.mDisplay);
+  if (!aInfo->HasVideo() && mVideoFrameContainer) {
+    // call ForgetElement() such that callbacks from |mVideoFrameContainer|
+    // won't reach us anymore.
+    mVideoFrameContainer->ForgetElement();
+    mVideoFrameContainer = nullptr;
   }
 }
 
 void HTMLMediaElement::FirstFrameLoaded()
 {
   NS_ASSERTION(!mSuspendedAfterFirstFrame, "Should not have already suspended");
 
   ChangeDelayLoadStatus(false);
--- a/dom/media/AbstractMediaDecoder.h
+++ b/dom/media/AbstractMediaDecoder.h
@@ -85,17 +85,16 @@ public:
   // 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(MediaInfo* aInfo, MetadataTags* aTags) = 0;
   virtual void QueueMetadata(int64_t aTime, MediaInfo* aInfo, MetadataTags* aTags) = 0;
-  virtual void FirstFrameLoaded(MediaInfo* aInfo) = 0;
 
   virtual void RemoveMediaTracks() = 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
@@ -173,63 +172,16 @@ class MetadataEventRunner : public nsRun
 
   // The ownership is transferred to MediaDecoder.
   MediaInfo* mInfo;
 
   // The ownership is transferred to its owning element.
   MetadataTags* mTags;
 };
 
-class FirstFrameLoadedEventRunner : public nsRunnable
-{
-  private:
-    nsRefPtr<AbstractMediaDecoder> mDecoder;
-  public:
-    FirstFrameLoadedEventRunner(AbstractMediaDecoder* aDecoder, MediaInfo* aInfo)
-          : mDecoder(aDecoder),
-            mInfo(aInfo)
-  {}
-
-  NS_IMETHOD Run() MOZ_OVERRIDE
-  {
-    mDecoder->FirstFrameLoaded(mInfo);
-    return NS_OK;
-  }
-
-  // The ownership is transferred to MediaDecoder.
-  MediaInfo* mInfo;
-};
-
-class MetadataUpdatedEventRunner : public nsRunnable
-{
-  private:
-    nsRefPtr<AbstractMediaDecoder> mDecoder;
-  public:
-    MetadataUpdatedEventRunner(AbstractMediaDecoder* aDecoder, MediaInfo* aInfo, MetadataTags* aTags)
-          : mDecoder(aDecoder),
-            mInfo(aInfo),
-            mTags(aTags)
-  {}
-
-  NS_IMETHOD Run() MOZ_OVERRIDE
-  {
-    nsAutoPtr<MediaInfo> info(new MediaInfo());
-    *info = *mInfo;
-    mDecoder->MetadataLoaded(info.forget(), mTags);
-    mDecoder->FirstFrameLoaded(mInfo);
-    return NS_OK;
-  }
-
-  // The ownership is transferred to MediaDecoder.
-  MediaInfo* mInfo;
-
-  // The ownership is transferred to its owning element.
-  MetadataTags* mTags;
-};
-
 class RemoveMediaTracksEventRunner : public nsRunnable
 {
 public:
   explicit RemoveMediaTracksEventRunner(AbstractMediaDecoder* aDecoder)
     : mDecoder(aDecoder)
   {}
 
   NS_IMETHOD Run() MOZ_OVERRIDE
--- a/dom/media/MediaDecoder.cpp
+++ b/dom/media/MediaDecoder.cpp
@@ -716,34 +716,16 @@ void MediaDecoder::MetadataLoaded(MediaI
   ConstructMediaTracks();
 
   if (mOwner) {
     // Make sure the element and the frame (if any) are told about
     // our new size.
     Invalidate();
     mOwner->MetadataLoaded(aInfo, aTags);
   }
-}
-
-void MediaDecoder::FirstFrameLoaded(MediaInfo* aInfo)
-{
-  MOZ_ASSERT(NS_IsMainThread());
-  if (mShuttingDown) {
-    return;
-  }
-
-  DECODER_LOG("FirstFrameLoaded, channels=%u rate=%u hasAudio=%d hasVideo=%d",
-              aInfo->mAudio.mChannels, aInfo->mAudio.mRate,
-              aInfo->HasAudio(), aInfo->HasVideo());
-
-  if (mPlayState == PLAY_STATE_LOADING && mIsDormant && !mIsExitingDormant) {
-    return;
-  }
-
-  mInfo = aInfo;
 
   if (mOwner) {
     mOwner->FirstFrameLoaded();
   }
 
   // This can run cache callbacks.
   mResource->EnsureCacheUpToDate();
 
--- a/dom/media/MediaDecoder.h
+++ b/dom/media/MediaDecoder.h
@@ -62,19 +62,17 @@ affect the change.
 An implementation of the MediaDecoderStateMachine class is the event
 that gets dispatched to the state machine thread. Each time the event is run,
 the state machine must cycle the state machine once, and then return.
 
 The state machine has the following states:
 
 DECODING_METADATA
   The media headers are being loaded, and things like framerate, etc are
-  being determined.
-DECODING_FIRSTFRAME
-  The first frame of audio/video data is being decoded.
+  being determined, and the first frame of audio/video data is being decoded.
 DECODING
   The decode has started. If the PlayState is PLAYING, the decode thread
   should be alive and decoding video and audio frame, the audio thread
   should be playing audio, and the state machine should run periodically
   to update the video frames being displayed.
 SEEKING
   A seek operation is in progress. The decode thread should be seeking.
 BUFFERING
@@ -101,46 +99,42 @@ Buffer
 Complete
   This is not user initiated. It occurs when the
   stream is completely decoded.
 Seek(double)
   Seek to the time position given in the resource.
 
 A state transition diagram:
 
-   |---<-- DECODING_METADATA ----->--------|
-   |        |                              |
- Seek(t)    v                          Shutdown()
-   |        |                              |
-   -->--- DECODING_FIRSTFRAME              |------->-----------------|
-            |        |                                               |
-            |    Shutdown()                                          |
-            |        |                                               |
-            v        |-->----------------->--------------------------|
-            |---------------->----->------------------------|        v
-          DECODING             |          |  |              |        |
-            ^                  v Seek(t)  |  |              |        |
-            |         Play()   |          v  |              |        |
-            ^-----------<----SEEKING      |  v Complete     v        v
-            |                  |          |  |              |        |
-            |                  |          |  COMPLETED    SHUTDOWN-<-|
-            ^                  ^          |  |Shutdown()    |
-            |                  |          |  >-------->-----^
-            |          Play()  |Seek(t)   |Buffer()         |
-            -----------<--------<-------BUFFERING           |
-                                          |                 ^
-                                          v Shutdown()      |
-                                          |                 |
-                                          ------------>-----|
+DECODING_METADATA
+  |      |
+  v      | Shutdown()
+  |      |
+  v      -->-------------------->--------------------------|
+  |---------------->----->------------------------|        v
+DECODING             |          |  |              |        |
+  ^                  v Seek(t)  |  |              |        |
+  |         Play()   |          v  |              |        |
+  ^-----------<----SEEKING      |  v Complete     v        v
+  |                  |          |  |              |        |
+  |                  |          |  COMPLETED    SHUTDOWN-<-|
+  ^                  ^          |  |Shutdown()    |
+  |                  |          |  >-------->-----^
+  |          Play()  |Seek(t)   |Buffer()         |
+  -----------<--------<-------BUFFERING           |
+                                |                 ^
+                                v Shutdown()      |
+                                |                 |
+                                ------------>-----|
 
 The following represents the states that the MediaDecoder object
 can be in, and the valid states the MediaDecoderStateMachine can be in at that
 time:
 
-player LOADING   decoder DECODING_METADATA, DECODING_FIRSTFRAME
+player LOADING   decoder DECODING_METADATA
 player PLAYING   decoder DECODING, BUFFERING, SEEKING, COMPLETED
 player PAUSED    decoder DECODING, BUFFERING, SEEKING, COMPLETED
 player SEEKING   decoder SEEKING
 player COMPLETED decoder SHUTDOWN
 player SHUTDOWN  decoder SHUTDOWN
 
 The general sequence of events is:
 
@@ -786,29 +780,29 @@ public:
   // 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.
   virtual void MetadataLoaded(MediaInfo* aInfo,
                               MetadataTags* aTags);
 
-  // Called when the first audio and/or video from the media file has been loaded
-  // by the state machine. Call on the main thread only.
-  virtual void FirstFrameLoaded(MediaInfo* aInfo);
-
   // Called from MetadataLoaded(). Creates audio tracks and adds them to its
   // owner's audio track list, and implies to video tracks respectively.
   // Call on the main thread only.
   void ConstructMediaTracks();
 
   // Removes all audio tracks and video tracks that are previously added into
   // the track list. Call on the main thread only.
   virtual void RemoveMediaTracks() MOZ_OVERRIDE;
 
+  // Called when the first frame has been loaded.
+  // Call on the main thread only.
+  void FirstFrameLoaded();
+
   // Returns true if the resource has been loaded. Acquires the monitor.
   // Call from any thread.
   virtual bool IsDataCachedToEndOfResource();
 
   // Called when the video has completed playing.
   // Call on the main thread only.
   void PlaybackEnded();
 
--- a/dom/media/MediaDecoderReader.h
+++ b/dom/media/MediaDecoderReader.h
@@ -96,20 +96,16 @@ public:
 
   // Read header data for all bitstreams in the file. Fills aInfo with
   // the data required to present the media, and optionally fills *aTags
   // with tag metadata from the file.
   // Returns NS_OK on success, or NS_ERROR_FAILURE on failure.
   virtual nsresult ReadMetadata(MediaInfo* aInfo,
                                 MetadataTags** aTags) = 0;
 
-  // Fills aInfo with the latest cached data required to present the media,
-  // ReadUpdatedMetadata will always be called once ReadMetadata has succeeded.
-  virtual void ReadUpdatedMetadata(MediaInfo* aInfo) { };
-
   // Requests the Reader to seek and call OnSeekCompleted on the callback
   // once completed.
   // Moves the decode head to aTime microseconds. aStartTime and aEndTime
   // denote the start and end times of the media in usecs, and aCurrentTime
   // is the current playback position in microseconds.
   virtual void Seek(int64_t aTime,
                     int64_t aStartTime,
                     int64_t aEndTime,
--- a/dom/media/MediaDecoderStateMachine.cpp
+++ b/dom/media/MediaDecoderStateMachine.cpp
@@ -366,18 +366,17 @@ void MediaDecoderStateMachine::SendStrea
   AssertCurrentThreadInMonitor();
   MOZ_ASSERT(mState != DECODER_STATE_DECODING_NONE);
 
   DecodedStreamData* stream = mDecoder->GetDecodedStream();
   if (!stream) {
     return;
   }
 
-  if (mState == DECODER_STATE_DECODING_METADATA ||
-      mState == DECODER_STATE_DECODING_FIRSTFRAME) {
+  if (mState == DECODER_STATE_DECODING_METADATA) {
     return;
   }
 
   // If there's still an audio sink alive, then we can't send any stream
   // data yet since both SendStreamData and the audio sink want to be in
   // charge of popping the audio queue. We're waiting for the audio sink
   if (mAudioSink) {
     return;
@@ -701,19 +700,19 @@ MediaDecoderStateMachine::OnAudioDecoded
   mAudioRequestPending = false;
 
   SAMPLE_LOG("OnAudioDecoded [%lld,%lld] disc=%d",
              (audio ? audio->mTime : -1),
              (audio ? audio->GetEndTime() : -1),
              (audio ? audio->mDiscontinuity : 0));
 
   switch (mState) {
-    case DECODER_STATE_DECODING_FIRSTFRAME: {
+    case DECODER_STATE_DECODING_METADATA: {
       Push(audio.forget());
-      MaybeFinishDecodeFirstFrame();
+      MaybeFinishDecodeMetadata();
       return;
     }
 
     case DECODER_STATE_BUFFERING:
     case DECODER_STATE_DECODING: {
       // In buffering and decoding state, we simply enqueue samples.
       Push(audio.forget());
       return;
@@ -766,17 +765,17 @@ MediaDecoderStateMachine::OnAudioDecoded
 void
 MediaDecoderStateMachine::Push(AudioData* aSample)
 {
   MOZ_ASSERT(aSample);
   // TODO: Send aSample to MSG and recalculate readystate before pushing,
   // otherwise AdvanceFrame may pop the sample before we have a chance
   // to reach playing.
   AudioQueue().Push(aSample);
-  if (mState > DECODER_STATE_DECODING_FIRSTFRAME) {
+  if (mState > DECODER_STATE_DECODING_METADATA) {
     SendStreamData();
     // The ready state can change when we've decoded data, so update the
     // ready state, so that DOM events can fire.
     UpdateReadyState();
     DispatchDecodeTasksIfNeeded();
     mDecoder->GetReentrantMonitor().NotifyAll();
   }
 }
@@ -784,17 +783,17 @@ MediaDecoderStateMachine::Push(AudioData
 void
 MediaDecoderStateMachine::Push(VideoData* aSample)
 {
   MOZ_ASSERT(aSample);
   // TODO: Send aSample to MSG and recalculate readystate before pushing,
   // otherwise AdvanceFrame may pop the sample before we have a chance
   // to reach playing.
   VideoQueue().Push(aSample);
-  if (mState > DECODER_STATE_DECODING_FIRSTFRAME) {
+  if (mState > DECODER_STATE_DECODING_METADATA) {
     SendStreamData();
     // The ready state can change when we've decoded data, so update the
     // ready state, so that DOM events can fire.
     UpdateReadyState();
     DispatchDecodeTasksIfNeeded();
     mDecoder->GetReentrantMonitor().NotifyAll();
   }
 }
@@ -835,18 +834,18 @@ MediaDecoderStateMachine::OnNotDecoded(M
     // Null sample. Hit end of stream. If we have decoded a frame,
     // insert it into the queue so that we have something to display.
     // We make sure to do this before invoking VideoQueue().Finish()
     // below.
     VideoQueue().Push(mFirstVideoFrameAfterSeek.forget());
   }
   isAudio ? AudioQueue().Finish() : VideoQueue().Finish();
   switch (mState) {
-    case DECODER_STATE_DECODING_FIRSTFRAME: {
-      MaybeFinishDecodeFirstFrame();
+    case DECODER_STATE_DECODING_METADATA: {
+      MaybeFinishDecodeMetadata();
       return;
     }
 
     case DECODER_STATE_BUFFERING:
     case DECODER_STATE_DECODING: {
       CheckIfDecodeComplete();
       SendStreamData();
       // The ready state can change when we've decoded data, so update the
@@ -879,24 +878,24 @@ MediaDecoderStateMachine::OnNotDecoded(M
 void
 MediaDecoderStateMachine::AcquireMonitorAndInvokeDecodeError()
 {
   ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor());
   DecodeError();
 }
 
 void
-MediaDecoderStateMachine::MaybeFinishDecodeFirstFrame()
+MediaDecoderStateMachine::MaybeFinishDecodeMetadata()
 {
   AssertCurrentThreadInMonitor();
   if ((IsAudioDecoding() && AudioQueue().GetSize() == 0) ||
       (IsVideoDecoding() && VideoQueue().GetSize() == 0)) {
     return;
   }
-  if (NS_FAILED(FinishDecodeFirstFrame())) {
+  if (NS_FAILED(FinishDecodeMetadata())) {
     DecodeError();
   }
 }
 
 void
 MediaDecoderStateMachine::OnVideoDecoded(VideoData* aVideoSample)
 {
   ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor());
@@ -904,19 +903,19 @@ MediaDecoderStateMachine::OnVideoDecoded
   mVideoRequestPending = false;
 
   SAMPLE_LOG("OnVideoDecoded [%lld,%lld] disc=%d",
              (video ? video->mTime : -1),
              (video ? video->GetEndTime() : -1),
              (video ? video->mDiscontinuity : 0));
 
   switch (mState) {
-    case DECODER_STATE_DECODING_FIRSTFRAME: {
+    case DECODER_STATE_DECODING_METADATA: {
       Push(video.forget());
-      MaybeFinishDecodeFirstFrame();
+      MaybeFinishDecodeMetadata();
       return;
     }
 
     case DECODER_STATE_BUFFERING:
     case DECODER_STATE_DECODING: {
       Push(video.forget());
       // If the requested video sample was slow to arrive, increase the
       // amount of audio we buffer to ensure that we don't run out of audio.
@@ -1217,17 +1216,16 @@ MediaDecoderOwner::NextFrameStatus Media
   }
   return MediaDecoderOwner::NEXT_FRAME_UNAVAILABLE;
 }
 
 static const char* const gMachineStateStr[] = {
   "NONE",
   "DECODING_METADATA",
   "WAIT_FOR_RESOURCES",
-  "DECODING_FIRSTFRAME",
   "DORMANT",
   "DECODING",
   "SEEKING",
   "BUFFERING",
   "COMPLETED",
   "SHUTDOWN"
 };
 
@@ -1524,65 +1522,22 @@ void MediaDecoderStateMachine::Seek(cons
   }
 
   // We need to be able to seek both at a transport level and at a media level
   // to seek.
   if (!mDecoder->IsMediaSeekable()) {
     DECODER_WARN("Seek() function should not be called on a non-seekable state machine");
     return;
   }
-
   // MediaDecoder::mPlayState should be SEEKING while we seek, and
   // in that case MediaDecoder shouldn't be calling us.
   NS_ASSERTION(mState != DECODER_STATE_SEEKING,
                "We shouldn't already be seeking");
-  NS_ASSERTION(mState > DECODER_STATE_DECODING_METADATA,
-               "We should have got duration already");
-
-  if (mState <= DECODER_STATE_DECODING_FIRSTFRAME) {
-    DECODER_LOG("Seek() Not Enough Data to continue at this stage, queuing seek");
-    mQueuedSeekTarget = aTarget;
-    return;
-  }
-  mQueuedSeekTarget.Reset();
-
-  StartSeek(aTarget);
-}
-
-void
-MediaDecoderStateMachine::EnqueueStartQueuedSeekTask()
-{
-  nsCOMPtr<nsIRunnable> event =
-    NS_NewRunnableMethod(this, &MediaDecoderStateMachine::StartQueuedSeek);
-  NS_DispatchToMainThread(event, NS_DISPATCH_NORMAL);
-}
-
-void
-MediaDecoderStateMachine::StartQueuedSeek()
-{
-  NS_ASSERTION(NS_IsMainThread(), "Should be on main thread.");
-  ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor());
-  if (!mQueuedSeekTarget.IsValid()) {
-    return;
-  }
-  StartSeek(mQueuedSeekTarget);
-  mQueuedSeekTarget.Reset();
-}
-
-void
-MediaDecoderStateMachine::StartSeek(const SeekTarget& aTarget)
-{
-  NS_ASSERTION(NS_IsMainThread(), "Should be on main thread.");
-  AssertCurrentThreadInMonitor();
-
-  MOZ_ASSERT(mState >= DECODER_STATE_DECODING);
-
-  if (mState == DECODER_STATE_SHUTDOWN) {
-    return;
-  }
+  NS_ASSERTION(mState >= DECODER_STATE_DECODING,
+               "We should have loaded metadata");
 
   // Bound the seek time to be inside the media range.
   NS_ASSERTION(mStartTime != -1, "Should know start time by now");
   NS_ASSERTION(mEndTime != -1, "Should know end time by now");
   int64_t seekTime = aTarget.mTime + mStartTime;
   seekTime = std::min(seekTime, mEndTime);
   seekTime = std::max(mStartTime, seekTime);
   NS_ASSERTION(seekTime >= mStartTime && seekTime <= mEndTime,
@@ -1594,18 +1549,18 @@ MediaDecoderStateMachine::StartSeek(cons
   if (mDecoder->GetDecodedStream()) {
     mDecoder->RecreateDecodedStream(seekTime - mStartTime);
   }
   ScheduleStateMachine();
 }
 
 void MediaDecoderStateMachine::StopAudioThread()
 {
-  NS_ASSERTION(OnDecodeThread() || OnStateMachineThread(),
-               "Should be on decode thread or state machine thread");
+  NS_ASSERTION(OnDecodeThread() ||
+               OnStateMachineThread(), "Should be on decode thread or state machine thread");
   AssertCurrentThreadInMonitor();
 
   if (mStopAudioThread) {
     // Audio sink is being stopped in another thread. Wait until finished.
     while (mAudioSink) {
       mDecoder->GetReentrantMonitor().Wait();
     }
     return;
@@ -1638,29 +1593,16 @@ MediaDecoderStateMachine::EnqueueDecodeM
 
   RefPtr<nsIRunnable> task(
     NS_NewRunnableMethod(this, &MediaDecoderStateMachine::CallDecodeMetadata));
   nsresult rv = mDecodeTaskQueue->Dispatch(task);
   NS_ENSURE_SUCCESS(rv, rv);
   return NS_OK;
 }
 
-nsresult
-MediaDecoderStateMachine::EnqueueDecodeFirstFrameTask()
-{
-  AssertCurrentThreadInMonitor();
-  MOZ_ASSERT(mState == DECODER_STATE_DECODING_FIRSTFRAME);
-
-  RefPtr<nsIRunnable> task(
-    NS_NewRunnableMethod(this, &MediaDecoderStateMachine::CallDecodeFirstFrame));
-  nsresult rv = mDecodeTaskQueue->Dispatch(task);
-  NS_ENSURE_SUCCESS(rv, rv);
-  return NS_OK;
-}
-
 void
 MediaDecoderStateMachine::SetReaderIdle()
 {
 #ifdef PR_LOGGING
   {
     ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor());
     DECODER_LOG("SetReaderIdle() audioQueue=%lld videoQueue=%lld",
                 GetDecodedAudioDuration(),
@@ -1786,17 +1728,17 @@ MediaDecoderStateMachine::EnsureAudioDec
 
   SAMPLE_LOG("EnsureAudioDecodeTaskQueued isDecoding=%d dispatched=%d",
               IsAudioDecoding(), mAudioRequestPending);
 
   if (mState >= DECODER_STATE_COMPLETED) {
     return NS_OK;
   }
 
-  MOZ_ASSERT(mState > DECODER_STATE_DECODING_FIRSTFRAME);
+  MOZ_ASSERT(mState > DECODER_STATE_DECODING_METADATA);
 
   if (IsAudioDecoding() && !mAudioRequestPending && !mWaitingForDecoderSeek) {
     RefPtr<nsIRunnable> task(
       NS_NewRunnableMethod(this, &MediaDecoderStateMachine::DecodeAudio));
     nsresult rv = mDecodeTaskQueue->Dispatch(task);
     if (NS_SUCCEEDED(rv)) {
       mAudioRequestPending = true;
     } else {
@@ -1831,17 +1773,17 @@ MediaDecoderStateMachine::EnsureVideoDec
 
   NS_ASSERTION(OnStateMachineThread() || OnDecodeThread(),
                "Should be on state machine or decode thread.");
 
   if (mState >= DECODER_STATE_COMPLETED) {
     return NS_OK;
   }
 
-  MOZ_ASSERT(mState > DECODER_STATE_DECODING_FIRSTFRAME);
+  MOZ_ASSERT(mState > DECODER_STATE_DECODING_METADATA);
 
   if (IsVideoDecoding() && !mVideoRequestPending && !mWaitingForDecoderSeek) {
     RefPtr<nsIRunnable> task(
       NS_NewRunnableMethod(this, &MediaDecoderStateMachine::DecodeVideo));
     nsresult rv = mDecodeTaskQueue->Dispatch(task);
     if (NS_SUCCEEDED(rv)) {
       mVideoRequestPending = true;
     } else {
@@ -1909,18 +1851,18 @@ bool MediaDecoderStateMachine::HasLowDec
 bool MediaDecoderStateMachine::HasLowUndecodedData()
 {
   return HasLowUndecodedData(mLowDataThresholdUsecs);
 }
 
 bool MediaDecoderStateMachine::HasLowUndecodedData(double aUsecs)
 {
   AssertCurrentThreadInMonitor();
-  NS_ASSERTION(mState > DECODER_STATE_DECODING_FIRSTFRAME,
-               "Must have loaded first frame for GetBuffered() to work");
+  NS_ASSERTION(mState > DECODER_STATE_DECODING_METADATA,
+               "Must have loaded metadata for GetBuffered() to work");
 
   bool reliable;
   double bytesPerSecond = mDecoder->ComputePlaybackRate(&reliable);
   if (!reliable) {
     // Default to assuming we have enough
     return false;
   }
 
@@ -2020,104 +1962,55 @@ nsresult MediaDecoderStateMachine::Decod
 
   if (NS_FAILED(res) || (!info.HasValidMedia())) {
     DECODER_WARN("ReadMetadata failed, res=%x HasValidMedia=%d", res, info.HasValidMedia());
     return NS_ERROR_FAILURE;
   }
   mDecoder->StartProgressUpdates();
   mGotDurationFromMetaData = (GetDuration() != -1);
 
-  if (mGotDurationFromMetaData) {
-    // We have all the information required: duration and size
-    // Inform the element that we've loaded the metadata.
-    EnqueueLoadedMetadataEvent();
-  }
-
-  if (mState == DECODER_STATE_DECODING_METADATA) {
-    SetState(DECODER_STATE_DECODING_FIRSTFRAME);
-    res = EnqueueDecodeFirstFrameTask();
-    if (NS_FAILED(res)) {
-      return NS_ERROR_FAILURE;
-    }
-  }
-  ScheduleStateMachine();
-
-  return NS_OK;
-}
-
-void
-MediaDecoderStateMachine::EnqueueLoadedMetadataEvent()
-{
-  nsAutoPtr<MediaInfo> info(new MediaInfo());
-  *info = mInfo;
-  nsCOMPtr<nsIRunnable> metadataLoadedEvent =
-    new MetadataEventRunner(mDecoder, info.forget(), mMetadataTags.forget());
-  NS_DispatchToMainThread(metadataLoadedEvent, NS_DISPATCH_NORMAL);
-}
-
-void
-MediaDecoderStateMachine::CallDecodeFirstFrame()
-{
-  ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor());
-  if (mState != DECODER_STATE_DECODING_FIRSTFRAME) {
-    return;
-  }
-  if (NS_FAILED(DecodeFirstFrame())) {
-    DECODER_WARN("Decode failed to start, shutting down decoder");
-    DecodeError();
-  }
-}
-
-nsresult
-MediaDecoderStateMachine::DecodeFirstFrame()
-{
-  AssertCurrentThreadInMonitor();
-  NS_ASSERTION(OnDecodeThread(), "Should be on decode thread.");
-  MOZ_ASSERT(mState == DECODER_STATE_DECODING_FIRSTFRAME);
-  DECODER_LOG("DecodeFirstFrame started");
-
   if (HasAudio()) {
     RefPtr<nsIRunnable> decodeTask(
       NS_NewRunnableMethod(this, &MediaDecoderStateMachine::DispatchAudioDecodeTaskIfNeeded));
     AudioQueue().AddPopListener(decodeTask, mDecodeTaskQueue);
   }
   if (HasVideo()) {
     RefPtr<nsIRunnable> decodeTask(
       NS_NewRunnableMethod(this, &MediaDecoderStateMachine::DispatchVideoDecodeTaskIfNeeded));
     VideoQueue().AddPopListener(decodeTask, mDecodeTaskQueue);
   }
 
   if (mScheduler->IsRealTime()) {
     SetStartTime(0);
-    nsresult res = FinishDecodeFirstFrame();
+    res = FinishDecodeMetadata();
     NS_ENSURE_SUCCESS(res, res);
   } else if (mDecodingFrozenAtStateMetadata) {
     SetStartTime(mStartTime);
-    nsresult res = FinishDecodeFirstFrame();
+    res = FinishDecodeMetadata();
     NS_ENSURE_SUCCESS(res, res);
   } else {
     if (HasAudio()) {
       ReentrantMonitorAutoExit unlock(mDecoder->GetReentrantMonitor());
       mReader->RequestAudioData();
     }
     if (HasVideo()) {
       ReentrantMonitorAutoExit unlock(mDecoder->GetReentrantMonitor());
       mReader->RequestVideoData(false, 0);
     }
   }
 
   return NS_OK;
 }
 
 nsresult
-MediaDecoderStateMachine::FinishDecodeFirstFrame()
+MediaDecoderStateMachine::FinishDecodeMetadata()
 {
   AssertCurrentThreadInMonitor();
   NS_ASSERTION(OnDecodeThread(), "Should be on decode thread.");
-  DECODER_LOG("FinishDecodeFirstFrame");
+  DECODER_LOG("FinishDecodeMetadata");
 
   if (mState == DECODER_STATE_SHUTDOWN) {
     return NS_ERROR_FAILURE;
   }
 
   if (!mScheduler->IsRealTime() && !mDecodingFrozenAtStateMetadata) {
     const VideoData* v = VideoQueue().PeekFront();
     const AudioData* a = AudioQueue().PeekFront();
@@ -2146,59 +2039,40 @@ MediaDecoderStateMachine::FinishDecodeFi
   if (HasAudio() && !HasVideo()) {
     // We're playing audio only. We don't need to worry about slow video
     // decodes causing audio underruns, so don't buffer so much audio in
     // order to reduce memory usage.
     mAmpleAudioThresholdUsecs /= NO_VIDEO_AMPLE_AUDIO_DIVISOR;
     mLowAudioThresholdUsecs /= NO_VIDEO_AMPLE_AUDIO_DIVISOR;
   }
 
-  // Get potentially updated metadata
-  {
-    ReentrantMonitorAutoExit exitMon(mDecoder->GetReentrantMonitor());
-    mReader->ReadUpdatedMetadata(&mInfo);
-  }
-
+  // Inform the element that we've loaded the metadata and the first frame.
   nsAutoPtr<MediaInfo> info(new MediaInfo());
   *info = mInfo;
-  nsCOMPtr<nsIRunnable> event;
-  if (!mGotDurationFromMetaData) {
-    // We now have a duration, we can fire the LoadedMetadata and
-    // FirstFrame event.
-    event =
-      new MetadataUpdatedEventRunner(mDecoder,
-                                     info.forget(),
-                                     mMetadataTags.forget());
-  } else {
-    // Inform the element that we've loaded the first frame.
-    event =
-      new FirstFrameLoadedEventRunner(mDecoder, info.forget());
-  }
-  NS_DispatchToMainThread(event, NS_DISPATCH_NORMAL);
-
-  if (mState == DECODER_STATE_DECODING_FIRSTFRAME) {
+  nsCOMPtr<nsIRunnable> metadataLoadedEvent =
+    new MetadataEventRunner(mDecoder, info.forget(), mMetadataTags.forget());
+  NS_DispatchToMainThread(metadataLoadedEvent, NS_DISPATCH_NORMAL);
+
+  if (mState == DECODER_STATE_DECODING_METADATA) {
+    DECODER_LOG("Changed state from DECODING_METADATA to DECODING");
     StartDecoding();
   }
 
-  // For very short media the first frame decode can decode the entire media.
+  // For very short media the metadata decode can decode the entire media.
   // So we need to check if this has occurred, else our decode pipeline won't
   // run (since it doesn't need to) and we won't detect end of stream.
   CheckIfDecodeComplete();
 
   if ((mState == DECODER_STATE_DECODING || mState == DECODER_STATE_COMPLETED) &&
       mDecoder->GetState() == MediaDecoder::PLAY_STATE_PLAYING &&
       !IsPlaying())
   {
     StartPlayback();
   }
 
-  if (mQueuedSeekTarget.IsValid()) {
-    EnqueueStartQueuedSeekTask();
-  }
-
   return NS_OK;
 }
 
 void MediaDecoderStateMachine::DecodeSeek()
 {
   ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor());
   NS_ASSERTION(OnDecodeThread(), "Should be on decode thread.");
   if (mState != DECODER_STATE_SEEKING) {
@@ -2335,17 +2209,16 @@ MediaDecoderStateMachine::SeekCompleted(
       NS_DispatchToMainThread(event, NS_DISPATCH_NORMAL);
     }
   }
 
   MOZ_ASSERT(mState != DECODER_STATE_DECODING_NONE);
 
   mDecoder->StartProgressUpdates();
   if (mState == DECODER_STATE_DECODING_METADATA ||
-      mState == DECODER_STATE_DECODING_FIRSTFRAME ||
       mState == DECODER_STATE_DORMANT ||
       mState == DECODER_STATE_SHUTDOWN) {
     return;
   }
 
   // Change state to DECODING or COMPLETED now. SeekingStopped will
   // call MediaDecoderStateMachine::Seek to reset our state to SEEKING
   // if we need to seek again.
@@ -2521,21 +2394,16 @@ nsresult MediaDecoderStateMachine::RunSt
       // Ensure we have a decode thread to decode metadata.
       return EnqueueDecodeMetadataTask();
     }
 
     case DECODER_STATE_DECODING_METADATA: {
       return NS_OK;
     }
 
-    case DECODER_STATE_DECODING_FIRSTFRAME: {
-      // DECODER_STATE_DECODING_FIRSTFRAME will be started by DecodeMetadata
-      return NS_OK;
-    }
-
     case DECODER_STATE_DECODING: {
       if (mDecoder->GetState() != MediaDecoder::PLAY_STATE_PLAYING &&
           IsPlaying())
       {
         // We're playing, but the element/decoder is in paused state. Stop
         // playing!
         StopPlayback();
       }
--- a/dom/media/MediaDecoderStateMachine.h
+++ b/dom/media/MediaDecoderStateMachine.h
@@ -133,17 +133,16 @@ public:
 
   nsresult Init(MediaDecoderStateMachine* aCloneDonor);
 
   // Enumeration for the valid decoding states
   enum State {
     DECODER_STATE_DECODING_NONE,
     DECODER_STATE_DECODING_METADATA,
     DECODER_STATE_WAIT_FOR_RESOURCES,
-    DECODER_STATE_DECODING_FIRSTFRAME,
     DECODER_STATE_DORMANT,
     DECODER_STATE_DECODING,
     DECODER_STATE_SEEKING,
     DECODER_STATE_BUFFERING,
     DECODER_STATE_COMPLETED,
     DECODER_STATE_SHUTDOWN
   };
 
@@ -194,32 +193,18 @@ public:
   MediaDecoderOwner::NextFrameStatus GetNextFrameStatus();
 
   // Cause state transitions. These methods obtain the decoder monitor
   // to synchronise the change of state, and to notify other threads
   // that the state has changed.
   void Play();
 
   // Seeks to the decoder to aTarget asynchronously.
-  // Must be called from the main thread.
   void Seek(const SeekTarget& aTarget);
 
-  // Dispatches a task to the main thread to seek to mQueuedSeekTarget.
-  // This is threadsafe and can be called on any thread.
-  void EnqueueStartQueuedSeekTask();
-
-  // Seeks to the decoder to mQueuedSeekTarget asynchronously.
-  // Must be called from the main thread.
-  void StartQueuedSeek();
-
-  // Seeks to the decoder to aTarget asynchronously.
-  // Must be called from the main thread.
-  // The decoder monitor must be held with exactly one lock count.
-  void StartSeek(const SeekTarget& aTarget);
-
   // Returns the current playback position in seconds.
   // Called from the main thread to get the current frame time. The decoder
   // monitor must be obtained before calling this.
   double GetCurrentTime() const;
 
   // Clear the flag indicating that a playback position change event
   // is currently queued. This is called from the main thread and must
   // be called with the decode monitor held.
@@ -407,17 +392,17 @@ protected:
     // on the media stream graph thread.
     MediaDecoderStateMachine* mStateMachine;
   };
   WakeDecoderRunnable* GetWakeDecoderRunnable();
 
   MediaQueue<AudioData>& AudioQueue() { return mAudioQueue; }
   MediaQueue<VideoData>& VideoQueue() { return mVideoQueue; }
 
-  nsresult FinishDecodeFirstFrame();
+  nsresult FinishDecodeMetadata();
 
   RefPtr<MediaDataDecodedListener<MediaDecoderStateMachine>> mMediaDecodedListener;
 
   nsAutoPtr<MetadataTags> mMetadataTags;
 
   // True if our buffers of decoded audio are not full, and we should
   // decode more.
   bool NeedToDecodeAudio();
@@ -536,26 +521,16 @@ protected:
 
   void StartWaitForResources();
 
   // Dispatches a task to the decode task queue to begin decoding metadata.
   // This is threadsafe and can be called on any thread.
   // The decoder monitor must be held.
   nsresult EnqueueDecodeMetadataTask();
 
-  // Dispatches a LoadedMetadataEvent.
-  // This is threadsafe and can be called on any thread.
-  // The decoder monitor must be held.
-  void EnqueueLoadedMetadataEvent();
-
-  // Dispatches a task to the decode task queue to begin decoding content.
-  // This is threadsafe and can be called on any thread.
-  // The decoder monitor must be held.
-  nsresult EnqueueDecodeFirstFrameTask();
-
   // Dispatches a task to the decode task queue to seek the decoder.
   // The decoder monitor must be held.
   nsresult EnqueueDecodeSeekTask();
 
   nsresult DispatchAudioDecodeTaskIfNeeded();
 
   // Ensures a to decode audio has been dispatched to the decode task queue.
   // If a task to decode has already been dispatched, this does nothing,
@@ -603,26 +578,19 @@ protected:
 
   // Load metadata. Called on the decode thread. The decoder monitor
   // must be held with exactly one lock count.
   nsresult DecodeMetadata();
 
   // Wraps the call to DecodeMetadata(), signals a DecodeError() on failure.
   void CallDecodeMetadata();
 
-  // Initiate first content decoding. Called on the decode thread.
-  // The decoder monitor must be held with exactly one lock count.
-  nsresult DecodeFirstFrame();
-
-  // Wraps the call to DecodeFirstFrame(), signals a DecodeError() on failure.
-  void CallDecodeFirstFrame();
-
-  // Checks whether we're finished decoding first audio and/or video packets,
-  // and switches to DECODING state if so.
-  void MaybeFinishDecodeFirstFrame();
+  // Checks whether we're finished decoding metadata, and switches to DECODING
+  // state if so.
+  void MaybeFinishDecodeMetadata();
 
   // Seeks to mSeekTarget. Called on the decode thread. The decoder monitor
   // must be held with exactly one lock count.
   void DecodeSeek();
 
   void CheckIfSeekComplete();
   bool IsAudioSeekComplete();
   bool IsVideoSeekComplete();
@@ -754,21 +722,16 @@ protected:
   // machine, decode, and main threads. Access controlled by decoder monitor.
   int64_t mEndTime;
 
   // Position to seek to in microseconds when the seek state transition occurs.
   // The decoder monitor lock must be obtained before reading or writing
   // this value. Accessed on main and decode thread.
   SeekTarget mSeekTarget;
 
-  // Position to seek to in microseconds when DecodeFirstFrame completes.
-  // The decoder monitor lock must be obtained before reading or writing
-  // this value. Accessed on main and decode thread.
-  SeekTarget mQueuedSeekTarget;
-
   // The position that we're currently seeking to. This differs from
   // mSeekTarget, as mSeekTarget is the target we'll seek to next, whereas
   // mCurrentSeekTarget is the position that the decode is in the process
   // of seeking to.
   // The decoder monitor lock must be obtained before reading or writing
   // this value.
   SeekTarget mCurrentSeekTarget;
 
--- a/dom/media/MediaMetadataManager.h
+++ b/dom/media/MediaMetadataManager.h
@@ -47,19 +47,19 @@ namespace mozilla {
         TimedMetadata* metadata = mMetadataQueue.getFirst();
         while (metadata && aCurrentTime >= static_cast<double>(metadata->mPublishTime) / USECS_PER_S) {
           // Remove all media tracks from the list first.
           nsCOMPtr<nsIRunnable> removeTracksEvent =
             new RemoveMediaTracksEventRunner(aDecoder);
           NS_DispatchToMainThread(removeTracksEvent);
 
           nsCOMPtr<nsIRunnable> metadataUpdatedEvent =
-            new MetadataUpdatedEventRunner(aDecoder,
-                                           metadata->mInfo.forget(),
-                                           metadata->mTags.forget());
+            new MetadataEventRunner(aDecoder,
+                                    metadata->mInfo.forget(),
+                                    metadata->mTags.forget());
           NS_DispatchToMainThread(metadataUpdatedEvent);
           delete mMetadataQueue.popFirst();
           metadata = mMetadataQueue.getFirst();
         }
       }
     protected:
       LinkedList<TimedMetadata> mMetadataQueue;
   };
--- a/dom/media/fmp4/MP4Reader.cpp
+++ b/dom/media/fmp4/MP4Reader.cpp
@@ -390,16 +390,20 @@ MP4Reader::ReadMetadata(MediaInfo* aInfo
     mInfo.mAudio.mChannels = audio.channel_count;
     mAudio.mCallback = new DecoderCallback(this, kAudio);
     mAudio.mDecoder = mPlatform->CreateAudioDecoder(audio,
                                                     mAudio.mTaskQueue,
                                                     mAudio.mCallback);
     NS_ENSURE_TRUE(mAudio.mDecoder != nullptr, NS_ERROR_FAILURE);
     nsresult rv = mAudio.mDecoder->Init();
     NS_ENSURE_SUCCESS(rv, rv);
+
+    // Decode one audio frame to detect potentially incorrect channels count or
+    // sampling rate from demuxer.
+    Decode(kAudio);
   }
 
   if (HasVideo()) {
     const VideoDecoderConfig& video = mDemuxer->VideoConfig();
     mInfo.mVideo.mDisplay =
       nsIntSize(video.display_width, video.display_height);
     mVideo.mCallback = new  DecoderCallback(this, kVideo);
     mVideo.mDecoder = mPlatform->CreateH264Decoder(video,
@@ -422,22 +426,16 @@ MP4Reader::ReadMetadata(MediaInfo* aInfo
   *aInfo = mInfo;
   *aTags = nullptr;
 
   UpdateIndex();
 
   return NS_OK;
 }
 
-void
-MP4Reader::ReadUpdatedMetadata(MediaInfo* aInfo)
-{
-  *aInfo = mInfo;
-}
-
 bool
 MP4Reader::IsMediaSeekable()
 {
   // We can seek if we get a duration *and* the reader reports that it's
   // seekable.
   return mDecoder->GetResource()->IsTransportSeekable() && mDemuxer->CanSeek();
 }
 
--- a/dom/media/fmp4/MP4Reader.h
+++ b/dom/media/fmp4/MP4Reader.h
@@ -40,18 +40,16 @@ public:
                                 int64_t aTimeThreshold) MOZ_OVERRIDE;
 
   virtual bool HasAudio() MOZ_OVERRIDE;
   virtual bool HasVideo() MOZ_OVERRIDE;
 
   virtual nsresult ReadMetadata(MediaInfo* aInfo,
                                 MetadataTags** aTags) MOZ_OVERRIDE;
 
-  virtual void ReadUpdatedMetadata(MediaInfo* aInfo) MOZ_OVERRIDE;
-
   virtual void Seek(int64_t aTime,
                     int64_t aStartTime,
                     int64_t aEndTime,
                     int64_t aCurrentTime) MOZ_OVERRIDE;
 
   virtual bool IsMediaSeekable() MOZ_OVERRIDE;
 
   virtual void NotifyDataArrived(const char* aBuffer, uint32_t aLength,
--- a/dom/media/mediasource/SourceBufferDecoder.cpp
+++ b/dom/media/mediasource/SourceBufferDecoder.cpp
@@ -91,22 +91,16 @@ SourceBufferDecoder::IsMediaSeekable()
 
 void
 SourceBufferDecoder::MetadataLoaded(MediaInfo* aInfo, MetadataTags* aTags)
 {
   MSE_DEBUG("SourceBufferDecoder(%p)::MetadataLoaded UNIMPLEMENTED", this);
 }
 
 void
-SourceBufferDecoder::FirstFrameLoaded(MediaInfo* aInfo)
-{
-  MSE_DEBUG("SourceBufferDecoder(%p)::FirstFrameLoaded UNIMPLEMENTED", this);
-}
-
-void
 SourceBufferDecoder::QueueMetadata(int64_t aTime, MediaInfo* aInfo, MetadataTags* aTags)
 {
   MSE_DEBUG("SourceBufferDecoder(%p)::QueueMetadata UNIMPLEMENTED", this);
 }
 
 void
 SourceBufferDecoder::RemoveMediaTracks()
 {
--- a/dom/media/mediasource/SourceBufferDecoder.h
+++ b/dom/media/mediasource/SourceBufferDecoder.h
@@ -43,17 +43,16 @@ public:
   virtual bool OnStateMachineThread() const MOZ_FINAL MOZ_OVERRIDE;
   virtual int64_t GetMediaDuration() MOZ_FINAL MOZ_OVERRIDE;
   virtual layers::ImageContainer* GetImageContainer() MOZ_FINAL MOZ_OVERRIDE;
   virtual MediaDecoderOwner* GetOwner() MOZ_FINAL MOZ_OVERRIDE;
   virtual SourceBufferResource* GetResource() const MOZ_FINAL MOZ_OVERRIDE;
   virtual ReentrantMonitor& GetReentrantMonitor() MOZ_FINAL MOZ_OVERRIDE;
   virtual VideoFrameContainer* GetVideoFrameContainer() MOZ_FINAL MOZ_OVERRIDE;
   virtual void MetadataLoaded(MediaInfo* aInfo, MetadataTags* aTags) MOZ_FINAL MOZ_OVERRIDE;
-  virtual void FirstFrameLoaded(MediaInfo* aInfo) MOZ_FINAL MOZ_OVERRIDE;
   virtual void NotifyBytesConsumed(int64_t aBytes, int64_t aOffset) MOZ_FINAL MOZ_OVERRIDE;
   virtual void NotifyDataArrived(const char* aBuffer, uint32_t aLength, int64_t aOffset) MOZ_FINAL MOZ_OVERRIDE;
   virtual void NotifyDecodedFrames(uint32_t aParsed, uint32_t aDecoded) MOZ_FINAL MOZ_OVERRIDE;
   virtual void NotifyWaitingForResourcesStatusChanged() MOZ_FINAL MOZ_OVERRIDE;
   virtual void OnReadMetadataCompleted() MOZ_FINAL MOZ_OVERRIDE;
   virtual void QueueMetadata(int64_t aTime, MediaInfo* aInfo, MetadataTags* aTags) MOZ_FINAL MOZ_OVERRIDE;
   virtual void RemoveMediaTracks() MOZ_FINAL MOZ_OVERRIDE;
   virtual void SetMediaDuration(int64_t aDuration) MOZ_FINAL MOZ_OVERRIDE;
--- a/dom/media/omx/MediaOmxCommonDecoder.cpp
+++ b/dom/media/omx/MediaOmxCommonDecoder.cpp
@@ -52,20 +52,21 @@ MediaOmxCommonDecoder::SetPlatformCanOff
 bool
 MediaOmxCommonDecoder::CheckDecoderCanOffloadAudio()
 {
   return (mCanOffloadAudio && !mFallbackToStateMachine && !mOutputStreams.Length() &&
       mInitialPlaybackRate == 1.0);
 }
 
 void
-MediaOmxCommonDecoder::FirstFrameLoaded(MediaInfo* aInfo)
+MediaOmxCommonDecoder::MetadataLoaded(MediaInfo* aInfo,
+                                      MetadataTags* aTags)
 {
   MOZ_ASSERT(NS_IsMainThread());
-  MediaDecoder::FirstFrameLoaded(aInfo);
+  MediaDecoder::MetadataLoaded(aInfo, aTags);
 
   ReentrantMonitorAutoEnter mon(GetReentrantMonitor());
   if (!CheckDecoderCanOffloadAudio()) {
     DECODER_LOG(PR_LOG_DEBUG, ("In %s Offload Audio check failed",
         __PRETTY_FUNCTION__));
     return;
   }
 
--- a/dom/media/omx/MediaOmxCommonDecoder.h
+++ b/dom/media/omx/MediaOmxCommonDecoder.h
@@ -18,17 +18,18 @@ namespace mozilla {
 class AudioOffloadPlayerBase;
 class MediaOmxCommonReader;
 
 class MediaOmxCommonDecoder : public MediaDecoder
 {
 public:
   MediaOmxCommonDecoder();
 
-  virtual void FirstFrameLoaded(MediaInfo* aInfo);
+  virtual void MetadataLoaded(MediaInfo* aInfo,
+                              MetadataTags* aTags);
   virtual void ChangeState(PlayState aState);
   virtual void ApplyStateToStateMachine(PlayState aState);
   virtual void SetVolume(double aVolume);
   virtual void PlaybackPositionChanged();
   virtual void UpdateReadyStateForData();
   virtual void SetElementVisibility(bool aIsVisible);
   virtual void SetPlatformCanOffloadAudio(bool aCanOffloadAudio);
   virtual bool CheckDecoderCanOffloadAudio();
--- a/dom/media/webaudio/BufferDecoder.cpp
+++ b/dom/media/webaudio/BufferDecoder.cpp
@@ -141,22 +141,16 @@ BufferDecoder::IsMediaSeekable()
 
 void
 BufferDecoder::MetadataLoaded(MediaInfo* aInfo, MetadataTags* aTags)
 {
   // ignore
 }
 
 void
-BufferDecoder::FirstFrameLoaded(MediaInfo* aInfo)
-{
-  // ignore
-}
-
-void
 BufferDecoder::QueueMetadata(int64_t aTime, MediaInfo* aInfo, MetadataTags* aTags)
 {
   // ignore
 }
 
 void
 BufferDecoder::RemoveMediaTracks()
 {
--- a/dom/media/webaudio/BufferDecoder.h
+++ b/dom/media/webaudio/BufferDecoder.h
@@ -55,17 +55,16 @@ public:
   virtual layers::ImageContainer* GetImageContainer() MOZ_FINAL MOZ_OVERRIDE;
 
   virtual bool IsTransportSeekable() MOZ_FINAL MOZ_OVERRIDE;
 
   virtual bool IsMediaSeekable() MOZ_FINAL MOZ_OVERRIDE;
 
   virtual void MetadataLoaded(MediaInfo* aInfo, MetadataTags* aTags) MOZ_FINAL MOZ_OVERRIDE;
   virtual void QueueMetadata(int64_t aTime, MediaInfo* aInfo, MetadataTags* aTags) MOZ_FINAL MOZ_OVERRIDE;
-  virtual void FirstFrameLoaded(MediaInfo* aInfo) MOZ_FINAL MOZ_OVERRIDE;
 
   virtual void RemoveMediaTracks() MOZ_FINAL MOZ_OVERRIDE;
 
   virtual void SetMediaEndTime(int64_t aTime) MOZ_FINAL MOZ_OVERRIDE;
 
   virtual void UpdatePlaybackPosition(int64_t aTime) MOZ_FINAL MOZ_OVERRIDE;
 
   virtual void OnReadMetadataCompleted() MOZ_FINAL MOZ_OVERRIDE;