Bug 1208316 - Route notifications of ending tracks through MediaStreamTrack instead of DOMMediaStream. r?jib
MozReview-Commit-ID: KMFE5HXEOtJ
--- a/dom/media/DOMMediaStream.cpp
+++ b/dom/media/DOMMediaStream.cpp
@@ -231,114 +231,121 @@ public:
{}
void Forget()
{
MOZ_ASSERT(NS_IsMainThread());
mStream = nullptr;
}
- void DoNotifyTrackEnded(MediaStream* aInputStream,
- TrackID aInputTrackID)
- {
- MOZ_ASSERT(NS_IsMainThread());
-
- if (!mStream) {
- return;
- }
-
- LOG(LogLevel::Debug, ("DOMMediaStream %p Track %u of stream %p ended",
- mStream, aInputTrackID, aInputStream));
-
- RefPtr<MediaStreamTrack> track =
- mStream->FindPlaybackDOMTrack(aInputStream, aInputTrackID);
- if (!track) {
- LOG(LogLevel::Debug, ("DOMMediaStream %p Not a playback track.", mStream));
- return;
- }
-
- LOG(LogLevel::Debug, ("DOMMediaStream %p Playback track; notifying stream listeners.",
- mStream));
- mStream->NotifyTrackRemoved(track);
-
- RefPtr<TrackPort> endedPort = mStream->FindPlaybackTrackPort(*track);
- NS_ASSERTION(endedPort, "Playback track should have a TrackPort");
- if (endedPort && IsTrackIDExplicit(endedPort->GetSourceTrackId())) {
- // If a track connected to a locked-track input port ends, we destroy the
- // port to allow our playback stream to finish.
- // XXX (bug 1208316) This should not be necessary when MediaStreams don't
- // finish but instead become inactive.
- endedPort->DestroyInputPort();
- }
- }
-
void DoNotifyFinishedTrackCreation()
{
MOZ_ASSERT(NS_IsMainThread());
if (!mStream) {
return;
}
// The owned stream listener adds its tracks after another main thread
// dispatch. We have to do the same to notify of created tracks to stay
// in sync. (Or NotifyTracksCreated is called before tracks are added).
NS_DispatchToMainThread(
NewRunnableMethod(mStream, &DOMMediaStream::NotifyTracksCreated));
}
- // The methods below are called on the MediaStreamGraph thread.
-
- void NotifyQueuedTrackChanges(MediaStreamGraph* aGraph, TrackID aID,
- StreamTime aTrackOffset, TrackEventCommand aTrackEvents,
- const MediaSegment& aQueuedMedia,
- MediaStream* aInputStream,
- TrackID aInputTrackID) override
+ void DoNotifyFinished()
{
- if (aTrackEvents & TrackEventCommand::TRACK_EVENT_ENDED) {
- nsCOMPtr<nsIRunnable> runnable =
- NewRunnableMethod<RefPtr<MediaStream>, TrackID>(
- this, &PlaybackStreamListener::DoNotifyTrackEnded, aInputStream, aInputTrackID);
- aGraph->DispatchToMainThreadAfterStreamStateUpdate(runnable.forget());
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (!mStream) {
+ return;
}
+
+ mStream->NotifyFinished();
}
+ // The methods below are called on the MediaStreamGraph thread.
+
void NotifyFinishedTrackCreation(MediaStreamGraph* aGraph) override
{
nsCOMPtr<nsIRunnable> runnable =
NewRunnableMethod(this, &PlaybackStreamListener::DoNotifyFinishedTrackCreation);
aGraph->DispatchToMainThreadAfterStreamStateUpdate(runnable.forget());
}
private:
// These fields may only be accessed on the main thread
DOMMediaStream* mStream;
};
+class DOMMediaStream::PlaybackTrackListener : public MediaStreamTrackConsumer
+{
+public:
+ explicit PlaybackTrackListener(DOMMediaStream* aStream) :
+ mStream(aStream) {}
+
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(PlaybackTrackListener,
+ MediaStreamTrackConsumer)
+
+ void NotifyEnded(MediaStreamTrack* aTrack) override
+ {
+ if (!mStream) {
+ MOZ_ASSERT(false);
+ return;
+ }
+
+ if (!aTrack) {
+ MOZ_ASSERT(false);
+ return;
+ }
+
+ MOZ_ASSERT(mStream->HasTrack(*aTrack));
+ mStream->NotifyTrackRemoved(aTrack);
+ }
+
+protected:
+ virtual ~PlaybackTrackListener() {}
+
+ RefPtr<DOMMediaStream> mStream;
+};
+
+NS_IMPL_ADDREF_INHERITED(DOMMediaStream::PlaybackTrackListener,
+ MediaStreamTrackConsumer)
+NS_IMPL_RELEASE_INHERITED(DOMMediaStream::PlaybackTrackListener,
+ MediaStreamTrackConsumer)
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(DOMMediaStream::PlaybackTrackListener)
+NS_INTERFACE_MAP_END_INHERITING(MediaStreamTrackConsumer)
+NS_IMPL_CYCLE_COLLECTION_INHERITED(DOMMediaStream::PlaybackTrackListener,
+ MediaStreamTrackConsumer,
+ mStream)
+
NS_IMPL_CYCLE_COLLECTION_CLASS(DOMMediaStream)
NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(DOMMediaStream,
DOMEventTargetHelper)
tmp->Destroy();
NS_IMPL_CYCLE_COLLECTION_UNLINK(mWindow)
NS_IMPL_CYCLE_COLLECTION_UNLINK(mOwnedTracks)
NS_IMPL_CYCLE_COLLECTION_UNLINK(mTracks)
NS_IMPL_CYCLE_COLLECTION_UNLINK(mConsumersToKeepAlive)
NS_IMPL_CYCLE_COLLECTION_UNLINK(mTrackSourceGetter)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mPlaybackTrackListener)
NS_IMPL_CYCLE_COLLECTION_UNLINK(mPrincipal)
NS_IMPL_CYCLE_COLLECTION_UNLINK(mVideoPrincipal)
NS_IMPL_CYCLE_COLLECTION_UNLINK_END
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(DOMMediaStream,
DOMEventTargetHelper)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mWindow)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mOwnedTracks)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mTracks)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mConsumersToKeepAlive)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mTrackSourceGetter)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPlaybackTrackListener)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPrincipal)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mVideoPrincipal)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
NS_IMPL_ADDREF_INHERITED(DOMMediaStream, DOMEventTargetHelper)
NS_IMPL_RELEASE_INHERITED(DOMMediaStream, DOMEventTargetHelper)
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(DOMMediaStream)
@@ -361,16 +368,17 @@ NS_IMPL_RELEASE_INHERITED(DOMAudioNodeMe
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(DOMAudioNodeMediaStream)
NS_INTERFACE_MAP_END_INHERITING(DOMMediaStream)
DOMMediaStream::DOMMediaStream(nsPIDOMWindowInner* aWindow,
MediaStreamTrackSourceGetter* aTrackSourceGetter)
: mLogicalStreamStartTime(0), mWindow(aWindow),
mInputStream(nullptr), mOwnedStream(nullptr), mPlaybackStream(nullptr),
mTracksPendingRemoval(0), mTrackSourceGetter(aTrackSourceGetter),
+ mPlaybackTrackListener(MakeAndAddRef<PlaybackTrackListener>(this)),
mTracksCreated(false), mNotifiedOfMediaStreamGraphShutdown(false),
mActive(false)
{
nsresult rv;
nsCOMPtr<nsIUUIDGenerator> uuidgen =
do_GetService("@mozilla.org/uuid-generator;1", &rv);
if (NS_SUCCEEDED(rv) && uuidgen) {
@@ -400,18 +408,22 @@ DOMMediaStream::Destroy()
}
if (mPlaybackListener) {
mPlaybackListener->Forget();
mPlaybackListener = nullptr;
}
for (const RefPtr<TrackPort>& info : mTracks) {
// We must remove ourselves from each track's principal change observer list
// before we die. CC may have cleared info->mTrack so guard against it.
- if (info->GetTrack()) {
- info->GetTrack()->RemovePrincipalChangeObserver(this);
+ MediaStreamTrack* track = info->GetTrack();
+ if (track) {
+ track->RemovePrincipalChangeObserver(this);
+ if (!track->Ended()) {
+ track->RemoveConsumer(mPlaybackTrackListener);
+ }
}
}
if (mPlaybackPort) {
mPlaybackPort->Destroy();
mPlaybackPort = nullptr;
}
if (mOwnedPort) {
mOwnedPort->Destroy();
@@ -1263,16 +1275,17 @@ DOMMediaStream::NotifyTrackAdded(const R
}
} else {
LOG(LogLevel::Debug, ("DOMMediaStream %p saw a track get added. "
"Recomputing principal.", this));
RecomputePrincipal();
}
aTrack->AddPrincipalChangeObserver(this);
+ aTrack->AddConsumer(mPlaybackTrackListener);
for (int32_t i = mTrackListeners.Length() - 1; i >= 0; --i) {
mTrackListeners[i]->NotifyTrackAdded(aTrack);
}
if (mActive) {
return;
}
@@ -1292,16 +1305,17 @@ DOMMediaStream::NotifyTrackAdded(const R
}
}
void
DOMMediaStream::NotifyTrackRemoved(const RefPtr<MediaStreamTrack>& aTrack)
{
MOZ_ASSERT(NS_IsMainThread());
+ aTrack->RemoveConsumer(mPlaybackTrackListener);
aTrack->RemovePrincipalChangeObserver(this);
for (int32_t i = mTrackListeners.Length() - 1; i >= 0; --i) {
mTrackListeners[i]->NotifyTrackRemoved(aTrack);
}
// Don't call RecomputePrincipal here as the track may still exist in the
--- a/dom/media/DOMMediaStream.h
+++ b/dom/media/DOMMediaStream.h
@@ -630,16 +630,19 @@ protected:
const RefPtr<MediaStreamTrack>& aTrack);
class OwnedStreamListener;
friend class OwnedStreamListener;
class PlaybackStreamListener;
friend class PlaybackStreamListener;
+ class PlaybackTrackListener;
+ friend class PlaybackTrackListener;
+
/**
* Block a track in our playback stream. Calls NotifyPlaybackTrackBlocked()
* after the MediaStreamGraph has applied the block and the track is no longer
* live.
*/
void BlockPlaybackTrack(TrackPort* aTrack);
/**
@@ -699,16 +702,19 @@ protected:
// Listener tracking changes to mOwnedStream. We use this to notify the
// MediaStreamTracks we own about state changes.
RefPtr<OwnedStreamListener> mOwnedListener;
// Listener tracking changes to mPlaybackStream. This drives state changes
// in this DOMMediaStream and notifications to mTrackListeners.
RefPtr<PlaybackStreamListener> mPlaybackListener;
+ // Listener tracking when live MediaStreamTracks in mTracks end.
+ RefPtr<PlaybackTrackListener> mPlaybackTrackListener;
+
nsTArray<nsAutoPtr<OnTracksAvailableCallback> > mRunOnTracksAvailable;
// Set to true after MediaStreamGraph has created tracks for mPlaybackStream.
bool mTracksCreated;
nsString mID;
// Keep these alive while the stream is alive.
--- a/dom/media/MediaStreamTrack.cpp
+++ b/dom/media/MediaStreamTrack.cpp
@@ -48,16 +48,24 @@ MediaStreamTrackSource::ApplyConstraints
{
RefPtr<PledgeVoid> p = new PledgeVoid();
p->Reject(new MediaStreamError(aWindow,
NS_LITERAL_STRING("OverconstrainedError"),
NS_LITERAL_STRING("")));
return p.forget();
}
+NS_IMPL_CYCLE_COLLECTING_ADDREF(MediaStreamTrackConsumer)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(MediaStreamTrackConsumer)
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(MediaStreamTrackConsumer)
+ NS_INTERFACE_MAP_ENTRY(nsISupports)
+NS_INTERFACE_MAP_END
+
+NS_IMPL_CYCLE_COLLECTION_0(MediaStreamTrackConsumer)
+
/**
* PrincipalHandleListener monitors changes in PrincipalHandle of the media flowing
* through the MediaStreamGraph.
*
* When the main thread principal for a MediaStreamTrack changes, its principal
* will be set to the combination of the previous principal and the new one.
*
* As a PrincipalHandle change later happens on the MediaStreamGraph thread, we will
@@ -166,25 +174,27 @@ MediaStreamTrack::Destroy()
}
}
NS_IMPL_CYCLE_COLLECTION_CLASS(MediaStreamTrack)
NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(MediaStreamTrack,
DOMEventTargetHelper)
tmp->Destroy();
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mConsumers)
NS_IMPL_CYCLE_COLLECTION_UNLINK(mOwningStream)
NS_IMPL_CYCLE_COLLECTION_UNLINK(mSource)
NS_IMPL_CYCLE_COLLECTION_UNLINK(mOriginalTrack)
NS_IMPL_CYCLE_COLLECTION_UNLINK(mPrincipal)
NS_IMPL_CYCLE_COLLECTION_UNLINK(mPendingPrincipal)
NS_IMPL_CYCLE_COLLECTION_UNLINK_END
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(MediaStreamTrack,
DOMEventTargetHelper)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mConsumers)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mOwningStream)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mSource)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mOriginalTrack)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPrincipal)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPendingPrincipal)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
NS_IMPL_ADDREF_INHERITED(MediaStreamTrack, DOMEventTargetHelper)
@@ -240,16 +250,18 @@ MediaStreamTrack::Stop()
MOZ_ASSERT(mOwningStream, "Every MediaStreamTrack needs an owning DOMMediaStream");
DOMMediaStream::TrackPort* port = mOwningStream->FindOwnedTrackPort(*this);
MOZ_ASSERT(port, "A MediaStreamTrack must exist in its owning DOMMediaStream");
RefPtr<Pledge<bool>> p = port->BlockSourceTrackId(mInputTrackID, BlockingMode::CREATION);
Unused << p;
mReadyState = MediaStreamTrackState::Ended;
+
+ NotifyEnded();
}
void
MediaStreamTrack::GetConstraints(dom::MediaTrackConstraints& aResult)
{
aResult = mConstraints;
}
@@ -352,30 +364,55 @@ MediaStreamTrack::NotifyPrincipalHandleC
this, GetPrincipalFromHandle(handle),
mPrincipal.get(), mPendingPrincipal.get()));
if (PrincipalHandleMatches(handle, mPendingPrincipal)) {
SetPrincipal(mPendingPrincipal);
mPendingPrincipal = nullptr;
}
}
+void
+MediaStreamTrack::NotifyEnded()
+{
+ MOZ_ASSERT(mReadyState == MediaStreamTrackState::Ended);
+
+ for (int32_t i = mConsumers.Length() - 1; i >= 0; --i) {
+ // Loop backwards by index in case the consumer removes itself in the
+ // callback.
+ mConsumers[i]->NotifyEnded(this);
+ }
+}
+
bool
MediaStreamTrack::AddPrincipalChangeObserver(
PrincipalChangeObserver<MediaStreamTrack>* aObserver)
{
return mPrincipalChangeObservers.AppendElement(aObserver) != nullptr;
}
bool
MediaStreamTrack::RemovePrincipalChangeObserver(
PrincipalChangeObserver<MediaStreamTrack>* aObserver)
{
return mPrincipalChangeObservers.RemoveElement(aObserver);
}
+void
+MediaStreamTrack::AddConsumer(MediaStreamTrackConsumer* aConsumer)
+{
+ MOZ_ASSERT(!mConsumers.Contains(aConsumer));
+ mConsumers.AppendElement(aConsumer);
+}
+
+void
+MediaStreamTrack::RemoveConsumer(MediaStreamTrackConsumer* aConsumer)
+{
+ mConsumers.RemoveElement(aConsumer);
+}
+
already_AddRefed<MediaStreamTrack>
MediaStreamTrack::Clone()
{
// MediaStreamTracks are currently governed by streams, so we need a dummy
// DOMMediaStream to own our track clone. The dummy will never see any
// dynamically created tracks (no input stream) so no need for a SourceGetter.
RefPtr<DOMMediaStream> newStream =
new DOMMediaStream(mOwningStream->GetParentObject(), nullptr);
@@ -418,16 +455,18 @@ MediaStreamTrack::OverrideEnded()
MOZ_ASSERT(false);
return;
}
mSource->UnregisterSink(this);
mReadyState = MediaStreamTrackState::Ended;
+ NotifyEnded();
+
DispatchTrustedEvent(NS_LITERAL_STRING("ended"));
}
DOMMediaStream*
MediaStreamTrack::GetInputDOMStream()
{
MediaStreamTrack* originalTrack =
mOriginalTrack ? mOriginalTrack.get() : this;
--- a/dom/media/MediaStreamTrack.h
+++ b/dom/media/MediaStreamTrack.h
@@ -226,16 +226,37 @@ public:
protected:
~BasicUnstoppableTrackSource() {}
const MediaSourceEnum mMediaSource;
};
/**
+ * Base class that consumers of a MediaStreamTrack can use to get notifications
+ * about state changes in the track.
+ */
+class MediaStreamTrackConsumer : public nsISupports
+{
+public:
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_CYCLE_COLLECTION_CLASS(MediaStreamTrackConsumer)
+
+ /**
+ * Called when the track's readyState transitions to "ended".
+ * Unlike the "ended" event exposed to script this is called for any reason,
+ * including MediaStreamTrack::Stop().
+ */
+ virtual void NotifyEnded(MediaStreamTrack* aTrack) {};
+
+protected:
+ virtual ~MediaStreamTrackConsumer() {}
+};
+
+/**
* Class representing a track in a DOMMediaStream.
*/
class MediaStreamTrack : public DOMEventTargetHelper,
public MediaStreamTrackSource::Sink
{
// DOMMediaStream owns MediaStreamTrack instances, and requires access to
// some internal state, e.g., GetInputStream(), GetOwnedStream().
friend class mozilla::DOMMediaStream;
@@ -316,16 +337,22 @@ public:
/**
* Called by the PrincipalHandleListener when this track's PrincipalHandle changes on
* the MediaStreamGraph thread. When the PrincipalHandle matches the pending
* principal we know that the principal change has propagated to consumers.
*/
void NotifyPrincipalHandleChanged(const PrincipalHandle& aPrincipalHandle);
/**
+ * Called when this track's readyState transitions to "ended".
+ * Notifies all MediaStreamTrackConsumers that this track ended.
+ */
+ void NotifyEnded();
+
+ /**
* Get this track's CORS mode.
*/
CORSMode GetCORSMode() const { return GetSource().GetCORSMode(); }
/**
* Get this track's PeerIdentity.
*/
const PeerIdentity* GetPeerIdentity() const { return GetSource().GetPeerIdentity(); }
@@ -359,16 +386,28 @@ public:
/**
* Remove an added PrincipalChangeObserver from this track.
*
* Returns true if it was successfully removed.
*/
bool RemovePrincipalChangeObserver(PrincipalChangeObserver<MediaStreamTrack>* aObserver);
/**
+ * Add a MediaStreamTrackConsumer to this track.
+ *
+ * Adding the same consumer multiple times is prohibited.
+ */
+ void AddConsumer(MediaStreamTrackConsumer* aConsumer);
+
+ /**
+ * Remove an added MediaStreamTrackConsumer from this track.
+ */
+ void RemoveConsumer(MediaStreamTrackConsumer* aConsumer);
+
+ /**
* Adds a MediaStreamTrackListener to the MediaStreamGraph representation of
* this track.
*/
void AddListener(MediaStreamTrackListener* aListener);
/**
* Removes a MediaStreamTrackListener from the MediaStreamGraph representation
* of this track.
@@ -424,16 +463,18 @@ protected:
* source as this MediaStreamTrack.
* aTrackID is the TrackID the new track will have in its owned stream.
*/
virtual already_AddRefed<MediaStreamTrack> CloneInternal(DOMMediaStream* aOwningStream,
TrackID aTrackID) = 0;
nsTArray<PrincipalChangeObserver<MediaStreamTrack>*> mPrincipalChangeObservers;
+ nsTArray<RefPtr<MediaStreamTrackConsumer>> mConsumers;
+
RefPtr<DOMMediaStream> mOwningStream;
TrackID mTrackID;
TrackID mInputTrackID;
RefPtr<MediaStreamTrackSource> mSource;
RefPtr<MediaStreamTrack> mOriginalTrack;
nsCOMPtr<nsIPrincipal> mPrincipal;
nsCOMPtr<nsIPrincipal> mPendingPrincipal;
RefPtr<PrincipalHandleListener> mPrincipalHandleListener;