Bug 1035819 - Patch 1 - Add notification of DirectListeners and generalize Notification of events. r=roc, a=2.0+
authorRandell Jesup <rjesup@jesup.org>
Mon, 14 Jul 2014 01:47:56 -0400
changeset 209018 0e4e020dbe8a84d751b2f66a7221b9422a939d08
parent 209017 ee3c7413a1f31c5018b1e0ba1e522f52e6fac614
child 209019 53075e07875f6df54da705efab7d4bf707772558
push id494
push userraliiev@mozilla.com
push dateMon, 25 Aug 2014 18:42:16 +0000
treeherdermozilla-release@a3cc3e46b571 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersroc, 2
bugs1035819
milestone32.0a2
Bug 1035819 - Patch 1 - Add notification of DirectListeners and generalize Notification of events. r=roc, a=2.0+
content/html/content/src/HTMLMediaElement.cpp
content/media/MediaDecoder.cpp
content/media/MediaDecoder.h
content/media/MediaStreamGraph.cpp
content/media/MediaStreamGraph.h
content/media/encoder/MediaEncoder.cpp
content/media/encoder/MediaEncoder.h
content/media/encoder/TrackEncoder.h
content/media/webspeech/recognition/SpeechStreamListener.cpp
content/media/webspeech/recognition/SpeechStreamListener.h
content/media/webspeech/synth/nsSpeechTask.cpp
dom/camera/CameraPreviewMediaStream.cpp
dom/media/MediaManager.h
--- a/content/html/content/src/HTMLMediaElement.cpp
+++ b/content/html/content/src/HTMLMediaElement.cpp
@@ -2729,21 +2729,24 @@ public:
     nsCOMPtr<nsIRunnable> event;
     if (aBlocked == BLOCKED) {
       event = NS_NewRunnableMethod(this, &StreamListener::DoNotifyBlocked);
     } else {
       event = NS_NewRunnableMethod(this, &StreamListener::DoNotifyUnblocked);
     }
     aGraph->DispatchToMainThreadAfterStreamStateUpdate(event.forget());
   }
-  virtual void NotifyFinished(MediaStreamGraph* aGraph) MOZ_OVERRIDE
+  virtual void NotifyEvent(MediaStreamGraph* aGraph,
+                           MediaStreamListener::MediaStreamGraphEvent event) MOZ_OVERRIDE
   {
-    nsCOMPtr<nsIRunnable> event =
-      NS_NewRunnableMethod(this, &StreamListener::DoNotifyFinished);
-    aGraph->DispatchToMainThreadAfterStreamStateUpdate(event.forget());
+    if (event == EVENT_FINISHED) {
+      nsCOMPtr<nsIRunnable> event =
+        NS_NewRunnableMethod(this, &StreamListener::DoNotifyFinished);
+      aGraph->DispatchToMainThreadAfterStreamStateUpdate(event.forget());
+    }
   }
   virtual void NotifyHasCurrentData(MediaStreamGraph* aGraph) MOZ_OVERRIDE
   {
     MutexAutoLock lock(mMutex);
     nsCOMPtr<nsIRunnable> event =
       NS_NewRunnableMethod(this, &StreamListener::DoNotifyHaveCurrentData);
     aGraph->DispatchToMainThreadAfterStreamStateUpdate(event.forget());
   }
--- a/content/media/MediaDecoder.cpp
+++ b/content/media/MediaDecoder.cpp
@@ -248,23 +248,25 @@ MediaDecoder::DecodedStreamGraphListener
     }
   }
 
   MutexAutoLock lock(mMutex);
   mStreamFinishedOnMainThread = true;
 }
 
 void
-MediaDecoder::DecodedStreamGraphListener::NotifyFinished(MediaStreamGraph* aGraph)
+MediaDecoder::DecodedStreamGraphListener::NotifyEvent(MediaStreamGraph* aGraph,
+  MediaStreamListener::MediaStreamGraphEvent event)
 {
-  nsCOMPtr<nsIRunnable> event =
-    NS_NewRunnableMethod(this, &DecodedStreamGraphListener::DoNotifyFinished);
-  aGraph->DispatchToMainThreadAfterStreamStateUpdate(event.forget());
+  if (event == EVENT_FINISHED) {
+    nsCOMPtr<nsIRunnable> event =
+      NS_NewRunnableMethod(this, &DecodedStreamGraphListener::DoNotifyFinished);
+    aGraph->DispatchToMainThreadAfterStreamStateUpdate(event.forget());
+  }
 }
-
 void MediaDecoder::DestroyDecodedStream()
 {
   MOZ_ASSERT(NS_IsMainThread());
   GetReentrantMonitor().AssertCurrentThreadIn();
 
   // All streams are having their SourceMediaStream disconnected, so they
   // need to be explicitly blocked again.
   for (int32_t i = mOutputStreams.Length() - 1; i >= 0; --i) {
--- a/content/media/MediaDecoder.h
+++ b/content/media/MediaDecoder.h
@@ -430,17 +430,18 @@ public:
     // mDecoderStateMachine is non-null and MediaDecoderStateMachine is false.
     bool mHaveBlockedForStateMachineNotPlaying;
   };
 
   class DecodedStreamGraphListener : public MediaStreamListener {
   public:
     DecodedStreamGraphListener(MediaStream* aStream, DecodedStreamData* aData);
     virtual void NotifyOutput(MediaStreamGraph* aGraph, GraphTime aCurrentTime) MOZ_OVERRIDE;
-    virtual void NotifyFinished(MediaStreamGraph* aGraph) MOZ_OVERRIDE;
+    virtual void NotifyEvent(MediaStreamGraph* aGraph,
+                             MediaStreamListener::MediaStreamGraphEvent event) MOZ_OVERRIDE;
 
     void DoNotifyFinished();
 
     StreamTime GetLastOutputTime()
     {
       MutexAutoLock lock(mMutex);
       return mLastOutputTime;
     }
--- a/content/media/MediaStreamGraph.cpp
+++ b/content/media/MediaStreamGraph.cpp
@@ -399,17 +399,17 @@ MediaStreamGraphImpl::UpdateCurrentTime(
   streamHasOutput.SetLength(mStreams.Length());
   for (uint32_t i = 0; i < mStreams.Length(); ++i) {
     MediaStream* stream = mStreams[i];
 
     // Calculate blocked time and fire Blocked/Unblocked events
     GraphTime blockedTime = 0;
     GraphTime t = prevCurrentTime;
     // include |nextCurrentTime| to ensure NotifyBlockingChanged() is called
-    // before NotifyFinished() when |nextCurrentTime == stream end time|
+    // before NotifyEvent(this, EVENT_FINISHED) when |nextCurrentTime == stream end time|
     while (t <= nextCurrentTime) {
       GraphTime end;
       bool blocked = stream->mBlocked.GetAt(t, &end);
       if (blocked) {
         blockedTime += std::min(end, nextCurrentTime) - t;
       }
       if (blocked != stream->mNotifiedBlocked) {
         for (uint32_t j = 0; j < stream->mListeners.Length(); ++j) {
@@ -462,17 +462,17 @@ MediaStreamGraphImpl::UpdateCurrentTime(
           stream->StreamTimeToGraphTime(stream->GetStreamBuffer().GetAllTracksEnd()))  {
       NS_WARN_IF_FALSE(stream->mNotifiedBlocked,
         "Should've notified blocked=true for a fully finished stream");
       stream->mNotifiedFinished = true;
       stream->mLastPlayedVideoFrame.SetNull();
       SetStreamOrderDirty();
       for (uint32_t j = 0; j < stream->mListeners.Length(); ++j) {
         MediaStreamListener* l = stream->mListeners[j];
-        l->NotifyFinished(this);
+        l->NotifyEvent(this, MediaStreamListener::EVENT_FINISHED);
       }
     }
   }
 }
 
 bool
 MediaStreamGraphImpl::WillUnderrun(MediaStream* aStream, GraphTime aTime,
                                    GraphTime aEndBlockingDecisions, GraphTime* aEnd)
@@ -1943,17 +1943,17 @@ MediaStream::EnsureTrack(TrackID aTrackI
   return track;
 }
 
 void
 MediaStream::RemoveAllListenersImpl()
 {
   for (int32_t i = mListeners.Length() - 1; i >= 0; --i) {
     nsRefPtr<MediaStreamListener> listener = mListeners[i].forget();
-    listener->NotifyRemoved(GraphImpl());
+    listener->NotifyEvent(GraphImpl(), MediaStreamListener::EVENT_REMOVED);
   }
   mListeners.Clear();
 }
 
 void
 MediaStream::DestroyImpl()
 {
   RemoveAllListenersImpl();
@@ -2121,17 +2121,17 @@ MediaStream::ChangeExplicitBlockerCount(
 
 void
 MediaStream::AddListenerImpl(already_AddRefed<MediaStreamListener> aListener)
 {
   MediaStreamListener* listener = *mListeners.AppendElement() = aListener;
   listener->NotifyBlockingChanged(GraphImpl(),
     mNotifiedBlocked ? MediaStreamListener::BLOCKED : MediaStreamListener::UNBLOCKED);
   if (mNotifiedFinished) {
-    listener->NotifyFinished(GraphImpl());
+    listener->NotifyEvent(GraphImpl(), MediaStreamListener::EVENT_FINISHED);
   }
   if (mNotifiedHasCurrentData) {
     listener->NotifyHasCurrentData(GraphImpl());
   }
 }
 
 void
 MediaStream::AddListener(MediaStreamListener* aListener)
@@ -2150,17 +2150,17 @@ MediaStream::AddListener(MediaStreamList
 }
 
 void
 MediaStream::RemoveListenerImpl(MediaStreamListener* aListener)
 {
   // wouldn't need this if we could do it in the opposite order
   nsRefPtr<MediaStreamListener> listener(aListener);
   mListeners.RemoveElement(aListener);
-  listener->NotifyRemoved(GraphImpl());
+  listener->NotifyEvent(GraphImpl(), MediaStreamListener::EVENT_REMOVED);
 }
 
 void
 MediaStream::RemoveListener(MediaStreamListener* aListener)
 {
   class Message : public ControlMessage {
   public:
     Message(MediaStream* aStream, MediaStreamListener* aListener) :
@@ -2377,25 +2377,47 @@ SourceMediaStream::NotifyDirectConsumers
     l->NotifyRealtimeData(static_cast<MediaStreamGraph*>(GraphImpl()), aTrack->mID, aTrack->mOutputRate,
                           offset, aTrack->mCommands, *aSegment);
   }
 }
 
 void
 SourceMediaStream::AddDirectListener(MediaStreamDirectListener* aListener)
 {
-  MutexAutoLock lock(mMutex);
-  mDirectListeners.AppendElement(aListener);
+  bool wasEmpty;
+  {
+    MutexAutoLock lock(mMutex);
+    wasEmpty = mDirectListeners.IsEmpty();
+    mDirectListeners.AppendElement(aListener);
+  }
+
+  if (wasEmpty) {
+    for (uint32_t j = 0; j < mListeners.Length(); ++j) {
+      MediaStreamListener* l = mListeners[j];
+      l->NotifyEvent(GraphImpl(), MediaStreamListener::EVENT_HAS_DIRECT_LISTENERS);
+    }
+  }
 }
 
 void
 SourceMediaStream::RemoveDirectListener(MediaStreamDirectListener* aListener)
 {
-  MutexAutoLock lock(mMutex);
-  mDirectListeners.RemoveElement(aListener);
+  bool isEmpty;
+  {
+    MutexAutoLock lock(mMutex);
+    mDirectListeners.RemoveElement(aListener);
+    isEmpty = mDirectListeners.IsEmpty();
+  }
+
+  if (isEmpty) {
+    for (uint32_t j = 0; j < mListeners.Length(); ++j) {
+      MediaStreamListener* l = mListeners[j];
+      l->NotifyEvent(GraphImpl(), MediaStreamListener::EVENT_HAS_NO_DIRECT_LISTENERS);
+    }
+  }
 }
 
 bool
 SourceMediaStream::HaveEnoughBuffered(TrackID aID)
 {
   MutexAutoLock lock(mMutex);
   TrackData *track = FindDataForTrack(aID);
   if (track) {
@@ -2465,17 +2487,17 @@ void
 SourceMediaStream::EndAllTrackAndFinish()
 {
   MutexAutoLock lock(mMutex);
   for (uint32_t i = 0; i < mUpdateTracks.Length(); ++i) {
     SourceMediaStream::TrackData* data = &mUpdateTracks[i];
     data->mCommands |= TRACK_END;
   }
   FinishWithLockHeld();
-  // we will call NotifyFinished() to let GetUserMedia know
+  // we will call NotifyEvent() to let GetUserMedia know
 }
 
 TrackTicks
 SourceMediaStream::GetBufferedTicks(TrackID aID)
 {
   StreamBuffer::Track* track  = mBuffer.FindTrack(aID);
   if (track) {
     MediaSegment* segment = track->GetSegment();
--- a/content/media/MediaStreamGraph.h
+++ b/content/media/MediaStreamGraph.h
@@ -106,16 +106,17 @@ protected:
 
 public:
   NS_INLINE_DECL_THREADSAFE_REFCOUNTING(MediaStreamListener)
 
   enum Consumption {
     CONSUMED,
     NOT_CONSUMED
   };
+
   /**
    * Notify that the stream is hooked up and we'd like to start or stop receiving
    * data on it. Only fires on SourceMediaStreams.
    * The initial state is assumed to be NOT_CONSUMED.
    */
   virtual void NotifyConsumptionChanged(MediaStreamGraph* aGraph, Consumption aConsuming) {}
 
   /**
@@ -152,26 +153,27 @@ public:
 
   /**
    * Notify that the stream output is advancing. aCurrentTime is the graph's
    * current time. MediaStream::GraphTimeToStreamTime can be used to get the
    * stream time.
    */
   virtual void NotifyOutput(MediaStreamGraph* aGraph, GraphTime aCurrentTime) {}
 
-  /**
-   * Notify that the stream finished.
-   */
-  virtual void NotifyFinished(MediaStreamGraph* aGraph) {}
+  enum MediaStreamGraphEvent {
+    EVENT_FINISHED,
+    EVENT_REMOVED,
+    EVENT_HAS_DIRECT_LISTENERS, // transition from no direct listeners
+    EVENT_HAS_NO_DIRECT_LISTENERS,  // transition to no direct listeners
+  };
 
   /**
-   * Notify that your listener has been removed, either due to RemoveListener(),
-   * or due to the stream being destroyed.  You will get no further notifications.
+   * Notify that an event has occurred on the Stream
    */
-  virtual void NotifyRemoved(MediaStreamGraph* aGraph) {}
+  virtual void NotifyEvent(MediaStreamGraph* aGraph, MediaStreamGraphEvent aEvent) {}
 
   enum {
     TRACK_EVENT_CREATED = 0x01,
     TRACK_EVENT_ENDED = 0x02
   };
   /**
    * Notify that changes to one of the stream tracks have been queued.
    * aTrackEvents can be any combination of TRACK_EVENT_CREATED and
--- a/content/media/encoder/MediaEncoder.cpp
+++ b/content/media/encoder/MediaEncoder.cpp
@@ -59,27 +59,27 @@ MediaEncoder::NotifyQueuedTrackChanges(M
   } else if (mVideoEncoder && aQueuedMedia.GetType() == MediaSegment::VIDEO) {
       mVideoEncoder->NotifyQueuedTrackChanges(aGraph, aID, aTrackRate,
                                               aTrackOffset, aTrackEvents,
                                               aQueuedMedia);
   }
 }
 
 void
-MediaEncoder::NotifyRemoved(MediaStreamGraph* aGraph)
+MediaEncoder::NotifyEvent(MediaStreamGraph* aGraph,
+                          MediaStreamListener::MediaStreamGraphEvent event)
 {
   // In case that MediaEncoder does not receive a TRACK_EVENT_ENDED event.
   LOG(PR_LOG_DEBUG, ("NotifyRemoved in [MediaEncoder]."));
   if (mAudioEncoder) {
-    mAudioEncoder->NotifyRemoved(aGraph);
+    mAudioEncoder->NotifyEvent(aGraph, event);
   }
   if (mVideoEncoder) {
-    mVideoEncoder->NotifyRemoved(aGraph);
+    mVideoEncoder->NotifyEvent(aGraph, event);
   }
-
 }
 
 /* static */
 already_AddRefed<MediaEncoder>
 MediaEncoder::CreateEncoder(const nsAString& aMIMEType, uint8_t aTrackTypes)
 {
 #ifdef PR_LOGGING
   if (!gMediaEncoderLog) {
--- a/content/media/encoder/MediaEncoder.h
+++ b/content/media/encoder/MediaEncoder.h
@@ -75,22 +75,23 @@ public :
   /**
    * Notified by the control loop of MediaStreamGraph; aQueueMedia is the raw
    * track data in form of MediaSegment.
    */
   virtual void NotifyQueuedTrackChanges(MediaStreamGraph* aGraph, TrackID aID,
                                         TrackRate aTrackRate,
                                         TrackTicks aTrackOffset,
                                         uint32_t aTrackEvents,
-                                        const MediaSegment& aQueuedMedia);
+                                        const MediaSegment& aQueuedMedia) MOZ_OVERRIDE;
 
   /**
    * Notified the stream is being removed.
    */
-  virtual void NotifyRemoved(MediaStreamGraph* aGraph);
+  virtual void NotifyEvent(MediaStreamGraph* aGraph,
+                           MediaStreamListener::MediaStreamGraphEvent event) MOZ_OVERRIDE;
 
   /**
    * Creates an encoder with a given MIME type. Returns null if we are unable
    * to create the encoder. For now, default aMIMEType to "audio/ogg" and use
    * Ogg+Opus if it is empty.
    */
   static already_AddRefed<MediaEncoder> CreateEncoder(const nsAString& aMIMEType,
                                                       uint8_t aTrackTypes = ContainerWriter::CREATE_AUDIO_TRACK);
--- a/content/media/encoder/TrackEncoder.h
+++ b/content/media/encoder/TrackEncoder.h
@@ -8,21 +8,20 @@
 
 #include "mozilla/ReentrantMonitor.h"
 
 #include "AudioSegment.h"
 #include "EncodedFrameContainer.h"
 #include "StreamBuffer.h"
 #include "TrackMetadataBase.h"
 #include "VideoSegment.h"
+#include "MediaStreamGraph.h"
 
 namespace mozilla {
 
-class MediaStreamGraph;
-
 /**
  * Base class of AudioTrackEncoder and VideoTrackEncoder. Lifetimes managed by
  * MediaEncoder. Most methods can only be called on the MediaEncoder's thread,
  * but some subclass methods can be called on other threads when noted.
  *
  * NotifyQueuedTrackChanges is called on subclasses of this class from the
  * MediaStreamGraph thread, and AppendAudioSegment/AppendVideoSegment is then
  * called to store media data in the TrackEncoder. Later on, GetEncodedTrack is
@@ -44,17 +43,23 @@ public:
                                         TrackTicks aTrackOffset,
                                         uint32_t aTrackEvents,
                                         const MediaSegment& aQueuedMedia) = 0;
 
   /**
    * Notified by the same callback of MediaEncoder when it has been removed from
    * MediaStreamGraph. Called on the MediaStreamGraph thread.
    */
-  void NotifyRemoved(MediaStreamGraph* aGraph) { NotifyEndOfStream(); }
+  void NotifyEvent(MediaStreamGraph* aGraph,
+                   MediaStreamListener::MediaStreamGraphEvent event)
+  {
+    if (event == MediaStreamListener::MediaStreamGraphEvent::EVENT_REMOVED) {
+      NotifyEndOfStream();
+    }
+  }
 
   /**
    * Creates and sets up meta data for a specific codec, called on the worker
    * thread.
    */
   virtual already_AddRefed<TrackMetadataBase> GetMetadata() = 0;
 
   /**
--- a/content/media/webspeech/recognition/SpeechStreamListener.cpp
+++ b/content/media/webspeech/recognition/SpeechStreamListener.cpp
@@ -79,15 +79,16 @@ SpeechStreamListener::ConvertAndDispatch
 
   int16_t* to = static_cast<int16_t*>(samples->Data());
   ConvertAudioSamplesWithScale(aData, to, aDuration, aVolume);
 
   mRecognition->FeedAudioData(samples.forget(), aDuration, this);
 }
 
 void
-SpeechStreamListener::NotifyFinished(MediaStreamGraph* aGraph)
+SpeechStreamListener::NotifyEvent(MediaStreamGraph* aGraph,
+                                  MediaStreamListener::MediaStreamGraphEvent event)
 {
   // TODO dispatch SpeechEnd event so services can be informed
 }
 
 } // namespace dom
 } // namespace mozilla
--- a/content/media/webspeech/recognition/SpeechStreamListener.h
+++ b/content/media/webspeech/recognition/SpeechStreamListener.h
@@ -25,17 +25,18 @@ public:
   ~SpeechStreamListener();
 
   void NotifyQueuedTrackChanges(MediaStreamGraph* aGraph, TrackID aID,
                                 TrackRate aTrackRate,
                                 TrackTicks aTrackOffset,
                                 uint32_t aTrackEvents,
                                 const MediaSegment& aQueuedMedia) MOZ_OVERRIDE;
 
-  void NotifyFinished(MediaStreamGraph* aGraph) MOZ_OVERRIDE;
+  void NotifyEvent(MediaStreamGraph* aGraph,
+                   MediaStreamListener::MediaStreamGraphEvent event) MOZ_OVERRIDE;
 
 private:
   template<typename SampleFormatType>
   void ConvertAndDispatchAudioChunk(int aDuration, float aVolume, SampleFormatType* aData);
   nsRefPtr<SpeechRecognition> mRecognition;
 };
 
 } // namespace dom
--- a/content/media/webspeech/synth/nsSpeechTask.cpp
+++ b/content/media/webspeech/synth/nsSpeechTask.cpp
@@ -44,38 +44,45 @@ public:
   void DoNotifyFinished()
   {
     if (mSpeechTask) {
       mSpeechTask->DispatchEndImpl(mSpeechTask->GetCurrentTime(),
                                    mSpeechTask->GetCurrentCharOffset());
     }
   }
 
-  virtual void NotifyFinished(MediaStreamGraph* aGraph)
+  virtual void NotifyEvent(MediaStreamGraph* aGraph,
+                           MediaStreamListener::MediaStreamGraphEvent event) MOZ_OVERRIDE
   {
-    nsCOMPtr<nsIRunnable> event =
-      NS_NewRunnableMethod(this, &SynthStreamListener::DoNotifyFinished);
-    aGraph->DispatchToMainThreadAfterStreamStateUpdate(event.forget());
+    switch (event) {
+      case EVENT_FINISHED:
+        {
+          nsCOMPtr<nsIRunnable> runnable =
+            NS_NewRunnableMethod(this, &SynthStreamListener::DoNotifyFinished);
+          aGraph->DispatchToMainThreadAfterStreamStateUpdate(runnable.forget());
+        }
+        break;
+      case EVENT_REMOVED:
+        mSpeechTask = nullptr;
+        break;
+      default:
+        break;
+    }
   }
 
   virtual void NotifyBlockingChanged(MediaStreamGraph* aGraph, Blocking aBlocked)
   {
     if (aBlocked == MediaStreamListener::UNBLOCKED && !mStarted) {
       mStarted = true;
       nsCOMPtr<nsIRunnable> event =
         NS_NewRunnableMethod(this, &SynthStreamListener::DoNotifyStarted);
       aGraph->DispatchToMainThreadAfterStreamStateUpdate(event.forget());
     }
   }
 
-  virtual void NotifyRemoved(MediaStreamGraph* aGraph)
-  {
-    mSpeechTask = nullptr;
-  }
-
 private:
   // Raw pointer; if we exist, the stream exists,
   // and 'mSpeechTask' exclusively owns it and therefor exists as well.
   nsSpeechTask* mSpeechTask;
 
   bool mStarted;
 };
 
--- a/dom/camera/CameraPreviewMediaStream.cpp
+++ b/dom/camera/CameraPreviewMediaStream.cpp
@@ -98,17 +98,17 @@ CameraPreviewMediaStream::AddListener(Me
 void
 CameraPreviewMediaStream::RemoveListener(MediaStreamListener* aListener)
 {
   MutexAutoLock lock(mMutex);
 
   MediaStreamGraph* gm = MediaStreamGraph::GetInstance();
   nsRefPtr<MediaStreamListener> listener(aListener);
   mListeners.RemoveElement(aListener);
-  listener->NotifyRemoved(gm);
+  listener->NotifyEvent(gm, MediaStreamListener::EVENT_REMOVED);
 }
 
 void
 CameraPreviewMediaStream::Destroy()
 {
   MutexAutoLock lock(mMutex);
   DestroyImpl();
 }
--- a/dom/media/MediaManager.h
+++ b/dom/media/MediaManager.h
@@ -170,20 +170,38 @@ public:
       mAudioSource->NotifyPull(aGraph, mStream, kAudioTrack, aDesiredTime, mLastEndTimeAudio);
     }
     if (mVideoSource) {
       mVideoSource->NotifyPull(aGraph, mStream, kVideoTrack, aDesiredTime, mLastEndTimeVideo);
     }
   }
 
   virtual void
-  NotifyFinished(MediaStreamGraph* aGraph) MOZ_OVERRIDE;
+  NotifyEvent(MediaStreamGraph* aGraph,
+              MediaStreamListener::MediaStreamGraphEvent event) MOZ_OVERRIDE
+  {
+    switch (event) {
+      case EVENT_FINISHED:
+        NotifyFinished(aGraph);
+        break;
+      case EVENT_REMOVED:
+        NotifyRemoved(aGraph);
+        break;
+      case EVENT_HAS_DIRECT_LISTENERS:
+      case EVENT_HAS_NO_DIRECT_LISTENERS:
+        //NotifyListenerEvent(aGraph, event);
+        break;
+    }
+  }
 
   virtual void
-  NotifyRemoved(MediaStreamGraph* aGraph) MOZ_OVERRIDE;
+  NotifyFinished(MediaStreamGraph* aGraph);
+
+  virtual void
+  NotifyRemoved(MediaStreamGraph* aGraph);
 
 private:
   // Set at construction
   nsCOMPtr<nsIThread> mMediaThread;
   uint64_t mWindowID;
 
   bool mStopped; // MainThread only