michael@0: /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-*/ michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this file, michael@0: * You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: #include "DOMMediaStream.h" michael@0: #include "nsContentUtils.h" michael@0: #include "mozilla/dom/MediaStreamBinding.h" michael@0: #include "mozilla/dom/LocalMediaStreamBinding.h" michael@0: #include "mozilla/dom/AudioNode.h" michael@0: #include "MediaStreamGraph.h" michael@0: #include "AudioStreamTrack.h" michael@0: #include "VideoStreamTrack.h" michael@0: michael@0: using namespace mozilla; michael@0: using namespace mozilla::dom; michael@0: michael@0: NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(DOMMediaStream) michael@0: NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY michael@0: NS_INTERFACE_MAP_ENTRY(nsISupports) michael@0: NS_INTERFACE_MAP_ENTRY(nsIDOMMediaStream) michael@0: NS_INTERFACE_MAP_END michael@0: michael@0: NS_IMPL_CYCLE_COLLECTING_ADDREF(DOMMediaStream) michael@0: NS_IMPL_CYCLE_COLLECTING_RELEASE(DOMMediaStream) michael@0: michael@0: NS_IMPL_CYCLE_COLLECTION_CLASS(DOMMediaStream) michael@0: michael@0: NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(DOMMediaStream) michael@0: tmp->Destroy(); michael@0: NS_IMPL_CYCLE_COLLECTION_UNLINK(mWindow) michael@0: NS_IMPL_CYCLE_COLLECTION_UNLINK(mTracks) michael@0: NS_IMPL_CYCLE_COLLECTION_UNLINK(mConsumersToKeepAlive) michael@0: NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER michael@0: NS_IMPL_CYCLE_COLLECTION_UNLINK_END michael@0: NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(DOMMediaStream) michael@0: NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mWindow) michael@0: NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mTracks) michael@0: NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mConsumersToKeepAlive) michael@0: NS_IMPL_CYCLE_COLLECTION_TRAVERSE_SCRIPT_OBJECTS michael@0: NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END michael@0: NS_IMPL_CYCLE_COLLECTION_TRACE_WRAPPERCACHE(DOMMediaStream) michael@0: michael@0: NS_IMPL_ISUPPORTS_INHERITED(DOMLocalMediaStream, DOMMediaStream, michael@0: nsIDOMLocalMediaStream) michael@0: michael@0: NS_IMPL_CYCLE_COLLECTION_INHERITED(DOMAudioNodeMediaStream, DOMMediaStream, michael@0: mStreamNode) michael@0: michael@0: NS_IMPL_ADDREF_INHERITED(DOMAudioNodeMediaStream, DOMMediaStream) michael@0: NS_IMPL_RELEASE_INHERITED(DOMAudioNodeMediaStream, DOMMediaStream) michael@0: michael@0: NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(DOMAudioNodeMediaStream) michael@0: NS_INTERFACE_MAP_END_INHERITING(DOMMediaStream) michael@0: michael@0: class DOMMediaStream::StreamListener : public MediaStreamListener { michael@0: public: michael@0: StreamListener(DOMMediaStream* aStream) michael@0: : mStream(aStream) michael@0: {} michael@0: michael@0: // Main thread only michael@0: void Forget() { mStream = nullptr; } michael@0: DOMMediaStream* GetStream() { return mStream; } michael@0: michael@0: class TrackChange : public nsRunnable { michael@0: public: michael@0: TrackChange(StreamListener* aListener, michael@0: TrackID aID, TrackTicks aTrackOffset, michael@0: uint32_t aEvents, MediaSegment::Type aType) michael@0: : mListener(aListener), mID(aID), mEvents(aEvents), mType(aType) michael@0: { michael@0: } michael@0: michael@0: NS_IMETHOD Run() michael@0: { michael@0: NS_ASSERTION(NS_IsMainThread(), "main thread only"); michael@0: michael@0: DOMMediaStream* stream = mListener->GetStream(); michael@0: if (!stream) { michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsRefPtr track; michael@0: if (mEvents & MediaStreamListener::TRACK_EVENT_CREATED) { michael@0: track = stream->CreateDOMTrack(mID, mType); michael@0: } else { michael@0: track = stream->GetDOMTrackFor(mID); michael@0: } michael@0: if (mEvents & MediaStreamListener::TRACK_EVENT_ENDED) { michael@0: track->NotifyEnded(); michael@0: } michael@0: return NS_OK; michael@0: } michael@0: michael@0: StreamTime mEndTime; michael@0: nsRefPtr mListener; michael@0: TrackID mID; michael@0: uint32_t mEvents; michael@0: MediaSegment::Type mType; michael@0: }; michael@0: michael@0: /** michael@0: * Notify that changes to one of the stream tracks have been queued. michael@0: * aTrackEvents can be any combination of TRACK_EVENT_CREATED and michael@0: * TRACK_EVENT_ENDED. aQueuedMedia is the data being added to the track michael@0: * at aTrackOffset (relative to the start of the stream). michael@0: * aQueuedMedia can be null if there is no output. michael@0: */ michael@0: virtual void NotifyQueuedTrackChanges(MediaStreamGraph* aGraph, TrackID aID, michael@0: TrackRate aTrackRate, michael@0: TrackTicks aTrackOffset, michael@0: uint32_t aTrackEvents, michael@0: const MediaSegment& aQueuedMedia) MOZ_OVERRIDE michael@0: { michael@0: if (aTrackEvents & (TRACK_EVENT_CREATED | TRACK_EVENT_ENDED)) { michael@0: nsRefPtr runnable = michael@0: new TrackChange(this, aID, aTrackOffset, aTrackEvents, michael@0: aQueuedMedia.GetType()); michael@0: NS_DispatchToMainThread(runnable); michael@0: } michael@0: } michael@0: michael@0: private: michael@0: // These fields may only be accessed on the main thread michael@0: DOMMediaStream* mStream; michael@0: }; michael@0: michael@0: DOMMediaStream::DOMMediaStream() michael@0: : mLogicalStreamStartTime(0), michael@0: mStream(nullptr), mHintContents(0), mTrackTypesAvailable(0), michael@0: mNotifiedOfMediaStreamGraphShutdown(false) michael@0: { michael@0: SetIsDOMBinding(); michael@0: } michael@0: michael@0: DOMMediaStream::~DOMMediaStream() michael@0: { michael@0: Destroy(); michael@0: } michael@0: michael@0: void michael@0: DOMMediaStream::Destroy() michael@0: { michael@0: if (mListener) { michael@0: mListener->Forget(); michael@0: mListener = nullptr; michael@0: } michael@0: if (mStream) { michael@0: mStream->Destroy(); michael@0: mStream = nullptr; michael@0: } michael@0: } michael@0: michael@0: JSObject* michael@0: DOMMediaStream::WrapObject(JSContext* aCx) michael@0: { michael@0: return dom::MediaStreamBinding::Wrap(aCx, this); michael@0: } michael@0: michael@0: double michael@0: DOMMediaStream::CurrentTime() michael@0: { michael@0: if (!mStream) { michael@0: return 0.0; michael@0: } michael@0: return MediaTimeToSeconds(mStream->GetCurrentTime() - mLogicalStreamStartTime); michael@0: } michael@0: michael@0: void michael@0: DOMMediaStream::GetAudioTracks(nsTArray >& aTracks) michael@0: { michael@0: for (uint32_t i = 0; i < mTracks.Length(); ++i) { michael@0: AudioStreamTrack* t = mTracks[i]->AsAudioStreamTrack(); michael@0: if (t) { michael@0: aTracks.AppendElement(t); michael@0: } michael@0: } michael@0: } michael@0: michael@0: void michael@0: DOMMediaStream::GetVideoTracks(nsTArray >& aTracks) michael@0: { michael@0: for (uint32_t i = 0; i < mTracks.Length(); ++i) { michael@0: VideoStreamTrack* t = mTracks[i]->AsVideoStreamTrack(); michael@0: if (t) { michael@0: aTracks.AppendElement(t); michael@0: } michael@0: } michael@0: } michael@0: michael@0: bool michael@0: DOMMediaStream::IsFinished() michael@0: { michael@0: return !mStream || mStream->IsFinished(); michael@0: } michael@0: michael@0: void michael@0: DOMMediaStream::InitSourceStream(nsIDOMWindow* aWindow, TrackTypeHints aHintContents) michael@0: { michael@0: mWindow = aWindow; michael@0: SetHintContents(aHintContents); michael@0: MediaStreamGraph* gm = MediaStreamGraph::GetInstance(); michael@0: InitStreamCommon(gm->CreateSourceStream(this)); michael@0: } michael@0: michael@0: void michael@0: DOMMediaStream::InitTrackUnionStream(nsIDOMWindow* aWindow, TrackTypeHints aHintContents) michael@0: { michael@0: mWindow = aWindow; michael@0: SetHintContents(aHintContents); michael@0: MediaStreamGraph* gm = MediaStreamGraph::GetInstance(); michael@0: InitStreamCommon(gm->CreateTrackUnionStream(this)); michael@0: } michael@0: michael@0: void michael@0: DOMMediaStream::InitStreamCommon(MediaStream* aStream) michael@0: { michael@0: mStream = aStream; michael@0: michael@0: // Setup track listener michael@0: mListener = new StreamListener(this); michael@0: aStream->AddListener(mListener); michael@0: } michael@0: michael@0: already_AddRefed michael@0: DOMMediaStream::CreateSourceStream(nsIDOMWindow* aWindow, TrackTypeHints aHintContents) michael@0: { michael@0: nsRefPtr stream = new DOMMediaStream(); michael@0: stream->InitSourceStream(aWindow, aHintContents); michael@0: return stream.forget(); michael@0: } michael@0: michael@0: already_AddRefed michael@0: DOMMediaStream::CreateTrackUnionStream(nsIDOMWindow* aWindow, TrackTypeHints aHintContents) michael@0: { michael@0: nsRefPtr stream = new DOMMediaStream(); michael@0: stream->InitTrackUnionStream(aWindow, aHintContents); michael@0: return stream.forget(); michael@0: } michael@0: michael@0: void michael@0: DOMMediaStream::SetTrackEnabled(TrackID aTrackID, bool aEnabled) michael@0: { michael@0: if (mStream) { michael@0: mStream->SetTrackEnabled(aTrackID, aEnabled); michael@0: } michael@0: } michael@0: michael@0: bool michael@0: DOMMediaStream::CombineWithPrincipal(nsIPrincipal* aPrincipal) michael@0: { michael@0: return nsContentUtils::CombineResourcePrincipals(&mPrincipal, aPrincipal); michael@0: } michael@0: michael@0: MediaStreamTrack* michael@0: DOMMediaStream::CreateDOMTrack(TrackID aTrackID, MediaSegment::Type aType) michael@0: { michael@0: MediaStreamTrack* track; michael@0: switch (aType) { michael@0: case MediaSegment::AUDIO: michael@0: track = new AudioStreamTrack(this, aTrackID); michael@0: mTrackTypesAvailable |= HINT_CONTENTS_AUDIO; michael@0: break; michael@0: case MediaSegment::VIDEO: michael@0: track = new VideoStreamTrack(this, aTrackID); michael@0: mTrackTypesAvailable |= HINT_CONTENTS_VIDEO; michael@0: break; michael@0: default: michael@0: MOZ_CRASH("Unhandled track type"); michael@0: } michael@0: mTracks.AppendElement(track); michael@0: michael@0: CheckTracksAvailable(); michael@0: michael@0: return track; michael@0: } michael@0: michael@0: MediaStreamTrack* michael@0: DOMMediaStream::GetDOMTrackFor(TrackID aTrackID) michael@0: { michael@0: for (uint32_t i = 0; i < mTracks.Length(); ++i) { michael@0: MediaStreamTrack* t = mTracks[i]; michael@0: // We may add streams to our track list that are actually owned by michael@0: // a different DOMMediaStream. Ignore those. michael@0: if (t->GetTrackID() == aTrackID && t->GetStream() == this) { michael@0: return t; michael@0: } michael@0: } michael@0: return nullptr; michael@0: } michael@0: michael@0: void michael@0: DOMMediaStream::NotifyMediaStreamGraphShutdown() michael@0: { michael@0: // No more tracks will ever be added, so just clear these callbacks now michael@0: // to prevent leaks. michael@0: mNotifiedOfMediaStreamGraphShutdown = true; michael@0: mRunOnTracksAvailable.Clear(); michael@0: michael@0: mConsumersToKeepAlive.Clear(); michael@0: } michael@0: michael@0: void michael@0: DOMMediaStream::NotifyStreamStateChanged() michael@0: { michael@0: if (IsFinished()) { michael@0: mConsumersToKeepAlive.Clear(); michael@0: } michael@0: } michael@0: michael@0: void michael@0: DOMMediaStream::OnTracksAvailable(OnTracksAvailableCallback* aRunnable) michael@0: { michael@0: if (mNotifiedOfMediaStreamGraphShutdown) { michael@0: // No more tracks will ever be added, so just delete the callback now. michael@0: delete aRunnable; michael@0: return; michael@0: } michael@0: mRunOnTracksAvailable.AppendElement(aRunnable); michael@0: CheckTracksAvailable(); michael@0: } michael@0: michael@0: void michael@0: DOMMediaStream::CheckTracksAvailable() michael@0: { michael@0: if (mTrackTypesAvailable == 0) { michael@0: return; michael@0: } michael@0: nsTArray > callbacks; michael@0: callbacks.SwapElements(mRunOnTracksAvailable); michael@0: michael@0: for (uint32_t i = 0; i < callbacks.Length(); ++i) { michael@0: OnTracksAvailableCallback* cb = callbacks[i]; michael@0: if (~mTrackTypesAvailable & cb->GetExpectedTracks()) { michael@0: // Some expected tracks not available yet. Try this callback again later. michael@0: *mRunOnTracksAvailable.AppendElement() = callbacks[i].forget(); michael@0: continue; michael@0: } michael@0: cb->NotifyTracksAvailable(this); michael@0: } michael@0: } michael@0: michael@0: DOMLocalMediaStream::~DOMLocalMediaStream() michael@0: { michael@0: if (mStream) { michael@0: // Make sure Listeners of this stream know it's going away michael@0: Stop(); michael@0: } michael@0: } michael@0: michael@0: JSObject* michael@0: DOMLocalMediaStream::WrapObject(JSContext* aCx) michael@0: { michael@0: return dom::LocalMediaStreamBinding::Wrap(aCx, this); michael@0: } michael@0: michael@0: void michael@0: DOMLocalMediaStream::Stop() michael@0: { michael@0: if (mStream && mStream->AsSourceStream()) { michael@0: mStream->AsSourceStream()->EndAllTrackAndFinish(); michael@0: } michael@0: } michael@0: michael@0: already_AddRefed michael@0: DOMLocalMediaStream::CreateSourceStream(nsIDOMWindow* aWindow, michael@0: TrackTypeHints aHintContents) michael@0: { michael@0: nsRefPtr stream = new DOMLocalMediaStream(); michael@0: stream->InitSourceStream(aWindow, aHintContents); michael@0: return stream.forget(); michael@0: } michael@0: michael@0: already_AddRefed michael@0: DOMLocalMediaStream::CreateTrackUnionStream(nsIDOMWindow* aWindow, michael@0: TrackTypeHints aHintContents) michael@0: { michael@0: nsRefPtr stream = new DOMLocalMediaStream(); michael@0: stream->InitTrackUnionStream(aWindow, aHintContents); michael@0: return stream.forget(); michael@0: } michael@0: michael@0: DOMAudioNodeMediaStream::DOMAudioNodeMediaStream(AudioNode* aNode) michael@0: : mStreamNode(aNode) michael@0: { michael@0: } michael@0: michael@0: already_AddRefed michael@0: DOMAudioNodeMediaStream::CreateTrackUnionStream(nsIDOMWindow* aWindow, michael@0: AudioNode* aNode, michael@0: TrackTypeHints aHintContents) michael@0: { michael@0: nsRefPtr stream = new DOMAudioNodeMediaStream(aNode); michael@0: stream->InitTrackUnionStream(aWindow, aHintContents); michael@0: return stream.forget(); michael@0: }