/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-*/ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this file, * You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "DOMMediaStream.h" #include "nsContentUtils.h" #include "nsServiceManagerUtils.h" #include "nsIScriptError.h" #include "nsIUUIDGenerator.h" #include "nsPIDOMWindow.h" #include "mozilla/TimerClamping.h" #include "mozilla/dom/MediaStreamBinding.h" #include "mozilla/dom/MediaStreamTrackEvent.h" #include "mozilla/dom/LocalMediaStreamBinding.h" #include "mozilla/dom/AudioNode.h" #include "AudioCaptureStream.h" #include "AudioChannelAgent.h" #include "mozilla/dom/AudioTrack.h" #include "mozilla/dom/AudioTrackList.h" #include "mozilla/dom/VideoTrack.h" #include "mozilla/dom/VideoTrackList.h" #include "mozilla/dom/HTMLCanvasElement.h" #include "mozilla/media/MediaUtils.h" #include "MediaStreamGraph.h" #include "MediaStreamListener.h" #include "AudioStreamTrack.h" #include "VideoStreamTrack.h" #include "Layers.h" // GetCurrentTime is defined in winbase.h as zero argument macro forwarding to // GetTickCount() and conflicts with NS_DECL_NSIDOMMEDIASTREAM, containing // currentTime getter. #ifdef GetCurrentTime #undef GetCurrentTime #endif #ifdef LOG #undef LOG #endif // GetCurrentTime is defined in winbase.h as zero argument macro forwarding to // GetTickCount() and conflicts with MediaStream::GetCurrentTime. #ifdef GetCurrentTime #undef GetCurrentTime #endif using namespace mozilla; using namespace mozilla::dom; using namespace mozilla::layers; using namespace mozilla::media; static LazyLogModule gMediaStreamLog("MediaStream"); #define LOG(type, msg) MOZ_LOG(gMediaStreamLog, type, msg) const TrackID TRACK_VIDEO_PRIMARY = 1; static bool ContainsLiveTracks(nsTArray>& aTracks) { for (auto& port : aTracks) { if (port->GetTrack()->ReadyState() == MediaStreamTrackState::Live) { return true; } } return false; } DOMMediaStream::TrackPort::TrackPort(MediaInputPort* aInputPort, MediaStreamTrack* aTrack, const InputPortOwnership aOwnership) : mInputPort(aInputPort) , mTrack(aTrack) , mOwnership(aOwnership) { MOZ_ASSERT(mInputPort); MOZ_ASSERT(mTrack); MOZ_COUNT_CTOR(TrackPort); } DOMMediaStream::TrackPort::~TrackPort() { MOZ_COUNT_DTOR(TrackPort); if (mOwnership == InputPortOwnership::OWNED) { DestroyInputPort(); } } void DOMMediaStream::TrackPort::DestroyInputPort() { if (mInputPort) { mInputPort->Destroy(); mInputPort = nullptr; } } MediaStream* DOMMediaStream::TrackPort::GetSource() const { return mInputPort ? mInputPort->GetSource() : nullptr; } TrackID DOMMediaStream::TrackPort::GetSourceTrackId() const { return mInputPort ? mInputPort->GetSourceTrackId() : TRACK_INVALID; } already_AddRefed> DOMMediaStream::TrackPort::BlockSourceTrackId(TrackID aTrackId, BlockingMode aBlockingMode) { if (mInputPort) { return mInputPort->BlockSourceTrackId(aTrackId, aBlockingMode); } RefPtr> rejected = new Pledge(); rejected->Reject(NS_ERROR_FAILURE); return rejected.forget(); } NS_IMPL_CYCLE_COLLECTION(DOMMediaStream::TrackPort, mTrack) NS_IMPL_CYCLE_COLLECTION_ROOT_NATIVE(DOMMediaStream::TrackPort, AddRef) NS_IMPL_CYCLE_COLLECTION_UNROOT_NATIVE(DOMMediaStream::TrackPort, Release) NS_IMPL_CYCLE_COLLECTING_ADDREF(MediaStreamTrackSourceGetter) NS_IMPL_CYCLE_COLLECTING_RELEASE(MediaStreamTrackSourceGetter) NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(MediaStreamTrackSourceGetter) NS_INTERFACE_MAP_ENTRY(nsISupports) NS_INTERFACE_MAP_END NS_IMPL_CYCLE_COLLECTION_0(MediaStreamTrackSourceGetter) /** * Listener registered on the Owned stream to detect added and ended owned * tracks for keeping the list of MediaStreamTracks in sync with the tracks * added and ended directly at the source. */ class DOMMediaStream::OwnedStreamListener : public MediaStreamListener { public: explicit OwnedStreamListener(DOMMediaStream* aStream) : mStream(aStream) {} void Forget() { mStream = nullptr; } void DoNotifyTrackCreated(TrackID aTrackID, MediaSegment::Type aType, MediaStream* aInputStream, TrackID aInputTrackID) { MOZ_ASSERT(NS_IsMainThread()); if (!mStream) { return; } MediaStreamTrack* track = mStream->FindOwnedDOMTrack(aInputStream, aInputTrackID, aTrackID); if (track) { LOG(LogLevel::Debug, ("DOMMediaStream %p Track %d from owned stream %p " "bound to MediaStreamTrack %p.", mStream, aTrackID, aInputStream, track)); return; } // Track had not been created on main thread before, create it now. NS_WARNING_ASSERTION( !mStream->mTracks.IsEmpty(), "A new track was detected on the input stream; creating a corresponding " "MediaStreamTrack. Initial tracks should be added manually to " "immediately and synchronously be available to JS."); RefPtr source; if (mStream->mTrackSourceGetter) { source = mStream->mTrackSourceGetter->GetMediaStreamTrackSource(aTrackID); } if (!source) { NS_ASSERTION(false, "Dynamic track created without an explicit TrackSource"); nsPIDOMWindowInner* window = mStream->GetParentObject(); nsIDocument* doc = window ? window->GetExtantDoc() : nullptr; nsIPrincipal* principal = doc ? doc->NodePrincipal() : nullptr; source = new BasicTrackSource(principal); } RefPtr newTrack = mStream->CreateDOMTrack(aTrackID, aType, source); NS_DispatchToMainThread(NewRunnableMethod>( mStream, &DOMMediaStream::AddTrackInternal, newTrack)); } void DoNotifyTrackEnded(MediaStream* aInputStream, TrackID aInputTrackID, TrackID aTrackID) { MOZ_ASSERT(NS_IsMainThread()); if (!mStream) { return; } RefPtr track = mStream->FindOwnedDOMTrack(aInputStream, aInputTrackID, aTrackID); NS_ASSERTION(track, "Owned MediaStreamTracks must be known by the DOMMediaStream"); if (track) { LOG(LogLevel::Debug, ("DOMMediaStream %p MediaStreamTrack %p ended at the source. Marking it ended.", mStream, track.get())); NS_DispatchToMainThread(NewRunnableMethod( track, &MediaStreamTrack::OverrideEnded)); } } void NotifyQueuedTrackChanges(MediaStreamGraph* aGraph, TrackID aID, StreamTime aTrackOffset, TrackEventCommand aTrackEvents, const MediaSegment& aQueuedMedia, MediaStream* aInputStream, TrackID aInputTrackID) override { if (aTrackEvents & TrackEventCommand::TRACK_EVENT_CREATED) { nsCOMPtr runnable = NewRunnableMethod, TrackID>( this, &OwnedStreamListener::DoNotifyTrackCreated, aID, aQueuedMedia.GetType(), aInputStream, aInputTrackID); aGraph->DispatchToMainThreadAfterStreamStateUpdate(runnable.forget()); } else if (aTrackEvents & TrackEventCommand::TRACK_EVENT_ENDED) { nsCOMPtr runnable = NewRunnableMethod, TrackID, TrackID>( this, &OwnedStreamListener::DoNotifyTrackEnded, aInputStream, aInputTrackID, aID); aGraph->DispatchToMainThreadAfterStreamStateUpdate(runnable.forget()); } } private: // These fields may only be accessed on the main thread DOMMediaStream* mStream; }; /** * Listener registered on the Playback stream to detect when tracks end and when * all new tracks this iteration have been created - for when several tracks are * queued by the source and committed all at once. */ class DOMMediaStream::PlaybackStreamListener : public MediaStreamListener { public: explicit PlaybackStreamListener(DOMMediaStream* aStream) : mStream(aStream) {} void Forget() { MOZ_ASSERT(NS_IsMainThread()); mStream = nullptr; } 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)); } void DoNotifyFinished() { MOZ_ASSERT(NS_IsMainThread()); if (!mStream) { return; } NS_DispatchToMainThread(NewRunnableMethod( mStream, &DOMMediaStream::NotifyFinished)); } // The methods below are called on the MediaStreamGraph thread. void NotifyFinishedTrackCreation(MediaStreamGraph* aGraph) override { nsCOMPtr runnable = NewRunnableMethod(this, &PlaybackStreamListener::DoNotifyFinishedTrackCreation); aGraph->DispatchToMainThreadAfterStreamStateUpdate(runnable.forget()); } void NotifyEvent(MediaStreamGraph* aGraph, MediaStreamGraphEvent event) override { if (event == MediaStreamGraphEvent::EVENT_FINISHED) { aGraph->DispatchToMainThreadAfterStreamStateUpdate( NewRunnableMethod(this, &PlaybackStreamListener::DoNotifyFinished)); } } 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 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) NS_INTERFACE_MAP_ENTRY(DOMMediaStream) NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper) NS_IMPL_ADDREF_INHERITED(DOMLocalMediaStream, DOMMediaStream) NS_IMPL_RELEASE_INHERITED(DOMLocalMediaStream, DOMMediaStream) NS_INTERFACE_MAP_BEGIN(DOMLocalMediaStream) NS_INTERFACE_MAP_ENTRY(DOMLocalMediaStream) NS_INTERFACE_MAP_END_INHERITING(DOMMediaStream) NS_IMPL_CYCLE_COLLECTION_INHERITED(DOMAudioNodeMediaStream, DOMMediaStream, mStreamNode) NS_IMPL_ADDREF_INHERITED(DOMAudioNodeMediaStream, DOMMediaStream) NS_IMPL_RELEASE_INHERITED(DOMAudioNodeMediaStream, DOMMediaStream) 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(this)), mTracksCreated(false), mNotifiedOfMediaStreamGraphShutdown(false), mActive(false), mSetInactiveOnFinish(false) { nsresult rv; nsCOMPtr uuidgen = do_GetService("@mozilla.org/uuid-generator;1", &rv); if (NS_SUCCEEDED(rv) && uuidgen) { nsID uuid; memset(&uuid, 0, sizeof(uuid)); rv = uuidgen->GenerateUUIDInPlace(&uuid); if (NS_SUCCEEDED(rv)) { char buffer[NSID_LENGTH]; uuid.ToProvidedString(buffer); mID = NS_ConvertASCIItoUTF16(buffer); } } } DOMMediaStream::~DOMMediaStream() { Destroy(); } void DOMMediaStream::Destroy() { LOG(LogLevel::Debug, ("DOMMediaStream %p Being destroyed.", this)); if (mOwnedListener) { mOwnedListener->Forget(); mOwnedListener = nullptr; } if (mPlaybackListener) { mPlaybackListener->Forget(); mPlaybackListener = nullptr; } for (const RefPtr& 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. 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(); mOwnedPort = nullptr; } if (mPlaybackStream) { mPlaybackStream->UnregisterUser(); mPlaybackStream = nullptr; } if (mOwnedStream) { mOwnedStream->UnregisterUser(); mOwnedStream = nullptr; } if (mInputStream) { mInputStream->UnregisterUser(); mInputStream = nullptr; } mRunOnTracksAvailable.Clear(); mTrackListeners.Clear(); } JSObject* DOMMediaStream::WrapObject(JSContext* aCx, JS::Handle aGivenProto) { return dom::MediaStreamBinding::Wrap(aCx, this, aGivenProto); } /* static */ already_AddRefed DOMMediaStream::Constructor(const GlobalObject& aGlobal, ErrorResult& aRv) { Sequence> emptyTrackSeq; return Constructor(aGlobal, emptyTrackSeq, aRv); } /* static */ already_AddRefed DOMMediaStream::Constructor(const GlobalObject& aGlobal, const DOMMediaStream& aStream, ErrorResult& aRv) { nsTArray> tracks; aStream.GetTracks(tracks); Sequence> nonNullTrackSeq; if (!nonNullTrackSeq.SetLength(tracks.Length(), fallible)) { MOZ_ASSERT(false); aRv.Throw(NS_ERROR_OUT_OF_MEMORY); return nullptr; } for (size_t i = 0; i < tracks.Length(); ++i) { nonNullTrackSeq[i] = tracks[i]; } return Constructor(aGlobal, nonNullTrackSeq, aRv); } /* static */ already_AddRefed DOMMediaStream::Constructor(const GlobalObject& aGlobal, const Sequence>& aTracks, ErrorResult& aRv) { nsCOMPtr ownerWindow = do_QueryInterface(aGlobal.GetAsSupports()); if (!ownerWindow) { aRv.Throw(NS_ERROR_FAILURE); return nullptr; } // Streams created from JS cannot have dynamically created tracks. MediaStreamTrackSourceGetter* getter = nullptr; RefPtr newStream = new DOMMediaStream(ownerWindow, getter); for (MediaStreamTrack& track : aTracks) { if (!newStream->GetPlaybackStream()) { MOZ_RELEASE_ASSERT(track.Graph()); newStream->InitPlaybackStreamCommon(track.Graph()); } newStream->AddTrack(track); } if (!newStream->GetPlaybackStream()) { MOZ_ASSERT(aTracks.IsEmpty()); MediaStreamGraph* graph = MediaStreamGraph::GetInstance(MediaStreamGraph::SYSTEM_THREAD_DRIVER, AudioChannel::Normal); newStream->InitPlaybackStreamCommon(graph); } return newStream.forget(); } double DOMMediaStream::CurrentTime() { if (!mPlaybackStream) { return 0.0; } return TimerClamping::ReduceSTimeValue(mPlaybackStream-> StreamTimeToSeconds(mPlaybackStream->GetCurrentTime() - mLogicalStreamStartTime)); } void DOMMediaStream::GetId(nsAString& aID) const { aID = mID; } void DOMMediaStream::GetAudioTracks(nsTArray >& aTracks) const { for (const RefPtr& info : mTracks) { AudioStreamTrack* t = info->GetTrack()->AsAudioStreamTrack(); if (t) { aTracks.AppendElement(t); } } } void DOMMediaStream::GetVideoTracks(nsTArray >& aTracks) const { for (const RefPtr& info : mTracks) { VideoStreamTrack* t = info->GetTrack()->AsVideoStreamTrack(); if (t) { aTracks.AppendElement(t); } } } void DOMMediaStream::GetTracks(nsTArray >& aTracks) const { for (const RefPtr& info : mTracks) { aTracks.AppendElement(info->GetTrack()); } } void DOMMediaStream::AddTrack(MediaStreamTrack& aTrack) { MOZ_RELEASE_ASSERT(mPlaybackStream); RefPtr dest = mPlaybackStream->AsProcessedStream(); MOZ_ASSERT(dest); if (!dest) { return; } LOG(LogLevel::Info, ("DOMMediaStream %p Adding track %p (from stream %p with ID %d)", this, &aTrack, aTrack.mOwningStream.get(), aTrack.mTrackID)); if (mPlaybackStream->Graph() != aTrack.Graph()) { NS_ASSERTION(false, "Cannot combine tracks from different MediaStreamGraphs"); LOG(LogLevel::Error, ("DOMMediaStream %p Own MSG %p != aTrack's MSG %p", this, mPlaybackStream->Graph(), aTrack.Graph())); nsAutoString trackId; aTrack.GetId(trackId); const char16_t* params[] = { trackId.get() }; nsCOMPtr pWindow = GetParentObject(); nsIDocument* document = pWindow ? pWindow->GetExtantDoc() : nullptr; nsContentUtils::ReportToConsole(nsIScriptError::errorFlag, NS_LITERAL_CSTRING("Media"), document, nsContentUtils::eDOM_PROPERTIES, "MediaStreamAddTrackDifferentAudioChannel", params, ArrayLength(params)); return; } if (HasTrack(aTrack)) { LOG(LogLevel::Debug, ("DOMMediaStream %p already contains track %p", this, &aTrack)); return; } // Hook up the underlying track with our underlying playback stream. RefPtr inputPort = GetPlaybackStream()->AllocateInputPort(aTrack.GetOwnedStream(), aTrack.mTrackID); RefPtr trackPort = new TrackPort(inputPort, &aTrack, TrackPort::InputPortOwnership::OWNED); mTracks.AppendElement(trackPort.forget()); NotifyTrackAdded(&aTrack); LOG(LogLevel::Debug, ("DOMMediaStream %p Added track %p", this, &aTrack)); } void DOMMediaStream::RemoveTrack(MediaStreamTrack& aTrack) { LOG(LogLevel::Info, ("DOMMediaStream %p Removing track %p (from stream %p with ID %d)", this, &aTrack, aTrack.mOwningStream.get(), aTrack.mTrackID)); RefPtr toRemove = FindPlaybackTrackPort(aTrack); if (!toRemove) { LOG(LogLevel::Debug, ("DOMMediaStream %p does not contain track %p", this, &aTrack)); return; } DebugOnly removed = mTracks.RemoveElement(toRemove); NS_ASSERTION(removed, "If there's a track port we should be able to remove it"); // If the track comes from a TRACK_ANY input port (i.e., mOwnedPort), we need // to block it in the port. Doing this for a locked track is still OK as it // will first block the track, then destroy the port. Both cause the track to // end. // If the track has already ended, it's input port might be gone, so in those // cases blocking the underlying track should be avoided. if (!aTrack.Ended()) { BlockPlaybackTrack(toRemove); NotifyTrackRemoved(&aTrack); } LOG(LogLevel::Debug, ("DOMMediaStream %p Removed track %p", this, &aTrack)); } class ClonedStreamSourceGetter : public MediaStreamTrackSourceGetter { public: NS_DECL_ISUPPORTS_INHERITED NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(ClonedStreamSourceGetter, MediaStreamTrackSourceGetter) explicit ClonedStreamSourceGetter(DOMMediaStream* aStream) : mStream(aStream) {} already_AddRefed GetMediaStreamTrackSource(TrackID aInputTrackID) override { MediaStreamTrack* sourceTrack = mStream->FindOwnedDOMTrack(mStream->GetOwnedStream(), aInputTrackID); MOZ_RELEASE_ASSERT(sourceTrack); return do_AddRef(&sourceTrack->GetSource()); } protected: virtual ~ClonedStreamSourceGetter() {} RefPtr mStream; }; NS_IMPL_ADDREF_INHERITED(ClonedStreamSourceGetter, MediaStreamTrackSourceGetter) NS_IMPL_RELEASE_INHERITED(ClonedStreamSourceGetter, MediaStreamTrackSourceGetter) NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(ClonedStreamSourceGetter) NS_INTERFACE_MAP_END_INHERITING(MediaStreamTrackSourceGetter) NS_IMPL_CYCLE_COLLECTION_INHERITED(ClonedStreamSourceGetter, MediaStreamTrackSourceGetter, mStream) already_AddRefed DOMMediaStream::Clone() { return CloneInternal(TrackForwardingOption::CURRENT); } already_AddRefed DOMMediaStream::CloneInternal(TrackForwardingOption aForwarding) { RefPtr newStream = new DOMMediaStream(GetParentObject(), new ClonedStreamSourceGetter(this)); LOG(LogLevel::Info, ("DOMMediaStream %p created clone %p, forwarding %s tracks", this, newStream.get(), aForwarding == TrackForwardingOption::ALL ? "all" : "current")); MOZ_RELEASE_ASSERT(mPlaybackStream); MOZ_RELEASE_ASSERT(mPlaybackStream->Graph()); MediaStreamGraph* graph = mPlaybackStream->Graph(); // We initiate the owned and playback streams first, since we need to create // all existing DOM tracks before we add the generic input port from // mInputStream to mOwnedStream (see AllocateInputPort wrt. destination // TrackID as to why). newStream->InitOwnedStreamCommon(graph); newStream->InitPlaybackStreamCommon(graph); // Set up existing DOM tracks. TrackID allocatedTrackID = 1; for (const RefPtr& info : mTracks) { MediaStreamTrack& track = *info->GetTrack(); LOG(LogLevel::Debug, ("DOMMediaStream %p forwarding external track %p to clone %p", this, &track, newStream.get())); RefPtr trackClone = newStream->CloneDOMTrack(track, allocatedTrackID++); } if (aForwarding == TrackForwardingOption::ALL) { // Set up an input port from our input stream to the new DOM stream's owned // stream, to allow for dynamically added tracks at the source to appear in // the clone. The clone may treat mInputStream as its own mInputStream but // ownership remains with us. newStream->mInputStream = mInputStream; if (mInputStream) { // We have already set up track-locked input ports for all existing DOM // tracks, so now we need to block those in the generic input port to // avoid ending up with double instances of them. nsTArray tracksToBlock; for (const RefPtr& info : mOwnedTracks) { tracksToBlock.AppendElement(info->GetTrack()->mTrackID); } newStream->mInputStream->RegisterUser(); newStream->mOwnedPort = newStream->mOwnedStream->AllocateInputPort(mInputStream, TRACK_ANY, TRACK_ANY, 0, 0, &tracksToBlock); } } return newStream.forget(); } bool DOMMediaStream::Active() const { return mActive; } MediaStreamTrack* DOMMediaStream::GetTrackById(const nsAString& aId) const { for (const RefPtr& info : mTracks) { nsString id; info->GetTrack()->GetId(id); if (id == aId) { return info->GetTrack(); } } return nullptr; } MediaStreamTrack* DOMMediaStream::GetOwnedTrackById(const nsAString& aId) { for (const RefPtr& info : mOwnedTracks) { nsString id; info->GetTrack()->GetId(id); if (id == aId) { return info->GetTrack(); } } return nullptr; } bool DOMMediaStream::HasTrack(const MediaStreamTrack& aTrack) const { return !!FindPlaybackTrackPort(aTrack); } bool DOMMediaStream::OwnsTrack(const MediaStreamTrack& aTrack) const { return !!FindOwnedTrackPort(aTrack); } bool DOMMediaStream::AddDirectListener(DirectMediaStreamListener* aListener) { if (GetInputStream() && GetInputStream()->AsSourceStream()) { GetInputStream()->AsSourceStream()->AddDirectListener(aListener); return true; // application should ignore NotifyQueuedTrackData } return false; } void DOMMediaStream::RemoveDirectListener(DirectMediaStreamListener* aListener) { if (GetInputStream() && GetInputStream()->AsSourceStream()) { GetInputStream()->AsSourceStream()->RemoveDirectListener(aListener); } } bool DOMMediaStream::IsFinished() const { return !mPlaybackStream || mPlaybackStream->IsFinished(); } void DOMMediaStream::SetInactiveOnFinish() { mSetInactiveOnFinish = true; } void DOMMediaStream::InitSourceStream(MediaStreamGraph* aGraph) { InitInputStreamCommon(aGraph->CreateSourceStream(), aGraph); InitOwnedStreamCommon(aGraph); InitPlaybackStreamCommon(aGraph); } void DOMMediaStream::InitTrackUnionStream(MediaStreamGraph* aGraph) { InitInputStreamCommon(aGraph->CreateTrackUnionStream(), aGraph); InitOwnedStreamCommon(aGraph); InitPlaybackStreamCommon(aGraph); } void DOMMediaStream::InitAudioCaptureStream(nsIPrincipal* aPrincipal, MediaStreamGraph* aGraph) { const TrackID AUDIO_TRACK = 1; RefPtr audioCaptureSource = new BasicTrackSource(aPrincipal, MediaSourceEnum::AudioCapture); AudioCaptureStream* audioCaptureStream = static_cast(aGraph->CreateAudioCaptureStream(AUDIO_TRACK)); InitInputStreamCommon(audioCaptureStream, aGraph); InitOwnedStreamCommon(aGraph); InitPlaybackStreamCommon(aGraph); RefPtr track = CreateDOMTrack(AUDIO_TRACK, MediaSegment::AUDIO, audioCaptureSource); AddTrackInternal(track); audioCaptureStream->Start(); } void DOMMediaStream::InitInputStreamCommon(MediaStream* aStream, MediaStreamGraph* aGraph) { MOZ_ASSERT(!mOwnedStream, "Input stream must be initialized before owned stream"); mInputStream = aStream; mInputStream->RegisterUser(); } void DOMMediaStream::InitOwnedStreamCommon(MediaStreamGraph* aGraph) { MOZ_ASSERT(!mPlaybackStream, "Owned stream must be initialized before playback stream"); mOwnedStream = aGraph->CreateTrackUnionStream(); mOwnedStream->SetAutofinish(true); mOwnedStream->RegisterUser(); if (mInputStream) { mOwnedPort = mOwnedStream->AllocateInputPort(mInputStream); } // Setup track listeners mOwnedListener = new OwnedStreamListener(this); mOwnedStream->AddListener(mOwnedListener); } void DOMMediaStream::InitPlaybackStreamCommon(MediaStreamGraph* aGraph) { mPlaybackStream = aGraph->CreateTrackUnionStream(); mPlaybackStream->SetAutofinish(true); mPlaybackStream->RegisterUser(); if (mOwnedStream) { mPlaybackPort = mPlaybackStream->AllocateInputPort(mOwnedStream); } mPlaybackListener = new PlaybackStreamListener(this); mPlaybackStream->AddListener(mPlaybackListener); LOG(LogLevel::Debug, ("DOMMediaStream %p Initiated with mInputStream=%p, mOwnedStream=%p, mPlaybackStream=%p", this, mInputStream, mOwnedStream, mPlaybackStream)); } already_AddRefed DOMMediaStream::CreateSourceStreamAsInput(nsPIDOMWindowInner* aWindow, MediaStreamGraph* aGraph, MediaStreamTrackSourceGetter* aTrackSourceGetter) { RefPtr stream = new DOMMediaStream(aWindow, aTrackSourceGetter); stream->InitSourceStream(aGraph); return stream.forget(); } already_AddRefed DOMMediaStream::CreateTrackUnionStreamAsInput(nsPIDOMWindowInner* aWindow, MediaStreamGraph* aGraph, MediaStreamTrackSourceGetter* aTrackSourceGetter) { RefPtr stream = new DOMMediaStream(aWindow, aTrackSourceGetter); stream->InitTrackUnionStream(aGraph); return stream.forget(); } already_AddRefed DOMMediaStream::CreateAudioCaptureStreamAsInput(nsPIDOMWindowInner* aWindow, nsIPrincipal* aPrincipal, MediaStreamGraph* aGraph) { // Audio capture doesn't create tracks dynamically MediaStreamTrackSourceGetter* getter = nullptr; RefPtr stream = new DOMMediaStream(aWindow, getter); stream->InitAudioCaptureStream(aPrincipal, aGraph); return stream.forget(); } void DOMMediaStream::PrincipalChanged(MediaStreamTrack* aTrack) { MOZ_ASSERT(aTrack); NS_ASSERTION(HasTrack(*aTrack), "Principal changed for an unknown track"); LOG(LogLevel::Info, ("DOMMediaStream %p Principal changed for track %p", this, aTrack)); RecomputePrincipal(); } void DOMMediaStream::RecomputePrincipal() { nsCOMPtr previousPrincipal = mPrincipal.forget(); nsCOMPtr previousVideoPrincipal = mVideoPrincipal.forget(); if (mTracksPendingRemoval > 0) { LOG(LogLevel::Info, ("DOMMediaStream %p RecomputePrincipal() Cannot " "recompute stream principal with tracks pending " "removal.", this)); return; } LOG(LogLevel::Debug, ("DOMMediaStream %p Recomputing principal. " "Old principal was %p.", this, previousPrincipal.get())); // mPrincipal is recomputed based on all current tracks, and tracks that have // not ended in our playback stream. for (const RefPtr& info : mTracks) { if (info->GetTrack()->Ended()) { continue; } LOG(LogLevel::Debug, ("DOMMediaStream %p Taking live track %p with " "principal %p into account.", this, info->GetTrack(), info->GetTrack()->GetPrincipal())); nsContentUtils::CombineResourcePrincipals(&mPrincipal, info->GetTrack()->GetPrincipal()); if (info->GetTrack()->AsVideoStreamTrack()) { nsContentUtils::CombineResourcePrincipals(&mVideoPrincipal, info->GetTrack()->GetPrincipal()); } } LOG(LogLevel::Debug, ("DOMMediaStream %p new principal is %p.", this, mPrincipal.get())); if (previousPrincipal != mPrincipal || previousVideoPrincipal != mVideoPrincipal) { NotifyPrincipalChanged(); } } void DOMMediaStream::NotifyPrincipalChanged() { if (!mPrincipal) { // When all tracks are removed, mPrincipal will change to nullptr. LOG(LogLevel::Info, ("DOMMediaStream %p Principal changed to nothing.", this)); } else { LOG(LogLevel::Info, ("DOMMediaStream %p Principal changed. Now: " "null=%d, codebase=%d, expanded=%d, system=%d", this, mPrincipal->GetIsNullPrincipal(), mPrincipal->GetIsCodebasePrincipal(), mPrincipal->GetIsExpandedPrincipal(), mPrincipal->GetIsSystemPrincipal())); } for (uint32_t i = 0; i < mPrincipalChangeObservers.Length(); ++i) { mPrincipalChangeObservers[i]->PrincipalChanged(this); } } bool DOMMediaStream::AddPrincipalChangeObserver( PrincipalChangeObserver* aObserver) { return mPrincipalChangeObservers.AppendElement(aObserver) != nullptr; } bool DOMMediaStream::RemovePrincipalChangeObserver( PrincipalChangeObserver* aObserver) { return mPrincipalChangeObservers.RemoveElement(aObserver); } void DOMMediaStream::AddTrackInternal(MediaStreamTrack* aTrack) { MOZ_ASSERT(aTrack->mOwningStream == this); MOZ_ASSERT(FindOwnedDOMTrack(aTrack->GetInputStream(), aTrack->mInputTrackID, aTrack->mTrackID)); MOZ_ASSERT(!FindPlaybackDOMTrack(aTrack->GetOwnedStream(), aTrack->mTrackID)); LOG(LogLevel::Debug, ("DOMMediaStream %p Adding owned track %p", this, aTrack)); mTracks.AppendElement( new TrackPort(mPlaybackPort, aTrack, TrackPort::InputPortOwnership::EXTERNAL)); NotifyTrackAdded(aTrack); DispatchTrackEvent(NS_LITERAL_STRING("addtrack"), aTrack); } already_AddRefed DOMMediaStream::CreateDOMTrack(TrackID aTrackID, MediaSegment::Type aType, MediaStreamTrackSource* aSource, const MediaTrackConstraints& aConstraints) { MOZ_RELEASE_ASSERT(mInputStream); MOZ_RELEASE_ASSERT(mOwnedStream); MOZ_ASSERT(FindOwnedDOMTrack(GetInputStream(), aTrackID) == nullptr); RefPtr track; switch (aType) { case MediaSegment::AUDIO: track = new AudioStreamTrack(this, aTrackID, aTrackID, aSource, aConstraints); break; case MediaSegment::VIDEO: track = new VideoStreamTrack(this, aTrackID, aTrackID, aSource, aConstraints); break; default: MOZ_CRASH("Unhandled track type"); } LOG(LogLevel::Debug, ("DOMMediaStream %p Created new track %p with ID %u", this, track.get(), aTrackID)); mOwnedTracks.AppendElement( new TrackPort(mOwnedPort, track, TrackPort::InputPortOwnership::EXTERNAL)); return track.forget(); } already_AddRefed DOMMediaStream::CloneDOMTrack(MediaStreamTrack& aTrack, TrackID aCloneTrackID) { MOZ_RELEASE_ASSERT(mOwnedStream); MOZ_RELEASE_ASSERT(mPlaybackStream); MOZ_RELEASE_ASSERT(IsTrackIDExplicit(aCloneTrackID)); TrackID inputTrackID = aTrack.mInputTrackID; MediaStream* inputStream = aTrack.GetInputStream(); RefPtr newTrack = aTrack.CloneInternal(this, aCloneTrackID); newTrack->mOriginalTrack = aTrack.mOriginalTrack ? aTrack.mOriginalTrack.get() : &aTrack; LOG(LogLevel::Debug, ("DOMMediaStream %p Created new track %p cloned from stream %p track %d", this, newTrack.get(), inputStream, inputTrackID)); RefPtr inputPort = mOwnedStream->AllocateInputPort(inputStream, inputTrackID, aCloneTrackID); mOwnedTracks.AppendElement( new TrackPort(inputPort, newTrack, TrackPort::InputPortOwnership::OWNED)); mTracks.AppendElement( new TrackPort(mPlaybackPort, newTrack, TrackPort::InputPortOwnership::EXTERNAL)); NotifyTrackAdded(newTrack); newTrack->SetEnabled(aTrack.Enabled()); newTrack->SetReadyState(aTrack.ReadyState()); if (aTrack.Ended()) { // For extra suspenders, make sure that we don't forward data by mistake // to the clone when the original has already ended. // We only block END_EXISTING to allow any pending clones to end. RefPtr> blockingPledge = inputPort->BlockSourceTrackId(inputTrackID, BlockingMode::END_EXISTING); Unused << blockingPledge; } return newTrack.forget(); } static DOMMediaStream::TrackPort* FindTrackPortAmongTracks(const MediaStreamTrack& aTrack, const nsTArray>& aTracks) { for (const RefPtr& info : aTracks) { if (info->GetTrack() == &aTrack) { return info; } } return nullptr; } MediaStreamTrack* DOMMediaStream::FindOwnedDOMTrack(MediaStream* aInputStream, TrackID aInputTrackID, TrackID aTrackID) const { MOZ_RELEASE_ASSERT(mOwnedStream); for (const RefPtr& info : mOwnedTracks) { if (info->GetInputPort() && info->GetInputPort()->GetSource() == aInputStream && info->GetTrack()->mInputTrackID == aInputTrackID && (aTrackID == TRACK_ANY || info->GetTrack()->mTrackID == aTrackID)) { // This track is owned externally but in our playback stream. return info->GetTrack(); } } return nullptr; } DOMMediaStream::TrackPort* DOMMediaStream::FindOwnedTrackPort(const MediaStreamTrack& aTrack) const { return FindTrackPortAmongTracks(aTrack, mOwnedTracks); } MediaStreamTrack* DOMMediaStream::FindPlaybackDOMTrack(MediaStream* aInputStream, TrackID aInputTrackID) const { if (!mPlaybackStream) { // One would think we can assert mPlaybackStream here, but track clones have // a dummy DOMMediaStream that doesn't have a playback stream, so we can't. return nullptr; } for (const RefPtr& info : mTracks) { if (info->GetInputPort() == mPlaybackPort && aInputStream == mOwnedStream && info->GetTrack()->mInputTrackID == aInputTrackID) { // This track is in our owned and playback streams. return info->GetTrack(); } if (info->GetInputPort() && info->GetInputPort()->GetSource() == aInputStream && info->GetSourceTrackId() == aInputTrackID) { // This track is owned externally but in our playback stream. MOZ_ASSERT(IsTrackIDExplicit(aInputTrackID)); return info->GetTrack(); } } return nullptr; } DOMMediaStream::TrackPort* DOMMediaStream::FindPlaybackTrackPort(const MediaStreamTrack& aTrack) const { return FindTrackPortAmongTracks(aTrack, mTracks); } void DOMMediaStream::OnTracksAvailable(OnTracksAvailableCallback* aRunnable) { if (mNotifiedOfMediaStreamGraphShutdown) { // No more tracks will ever be added, so just delete the callback now. delete aRunnable; return; } mRunOnTracksAvailable.AppendElement(aRunnable); CheckTracksAvailable(); } void DOMMediaStream::NotifyTracksCreated() { mTracksCreated = true; CheckTracksAvailable(); } void DOMMediaStream::NotifyFinished() { if (!mSetInactiveOnFinish) { return; } if (!mActive) { // This can happen if the stream never became active. return; } MOZ_ASSERT(!ContainsLiveTracks(mTracks)); mActive = false; NotifyInactive(); } void DOMMediaStream::NotifyActive() { LOG(LogLevel::Info, ("DOMMediaStream %p NotifyActive(). ", this)); MOZ_ASSERT(mActive); for (int32_t i = mTrackListeners.Length() - 1; i >= 0; --i) { mTrackListeners[i]->NotifyActive(); } } void DOMMediaStream::NotifyInactive() { LOG(LogLevel::Info, ("DOMMediaStream %p NotifyInactive(). ", this)); MOZ_ASSERT(!mActive); for (int32_t i = mTrackListeners.Length() - 1; i >= 0; --i) { mTrackListeners[i]->NotifyInactive(); } } void DOMMediaStream::CheckTracksAvailable() { if (!mTracksCreated) { return; } nsTArray > callbacks; callbacks.SwapElements(mRunOnTracksAvailable); for (uint32_t i = 0; i < callbacks.Length(); ++i) { callbacks[i]->NotifyTracksAvailable(this); } } void DOMMediaStream::RegisterTrackListener(TrackListener* aListener) { MOZ_ASSERT(NS_IsMainThread()); if (mNotifiedOfMediaStreamGraphShutdown) { // No more tracks will ever be added, so just do nothing. return; } mTrackListeners.AppendElement(aListener); } void DOMMediaStream::UnregisterTrackListener(TrackListener* aListener) { MOZ_ASSERT(NS_IsMainThread()); mTrackListeners.RemoveElement(aListener); } void DOMMediaStream::NotifyTrackAdded(const RefPtr& aTrack) { MOZ_ASSERT(NS_IsMainThread()); if (mTracksPendingRemoval > 0) { // If there are tracks pending removal we may not degrade the current // principals until those tracks have been confirmed removed from the // playback stream. Instead combine with the new track and the (potentially) // degraded principal will be calculated when it's safe. nsContentUtils::CombineResourcePrincipals(&mPrincipal, aTrack->GetPrincipal()); LOG(LogLevel::Debug, ("DOMMediaStream %p saw a track get added. Combining " "its principal %p into our while waiting for pending " "tracks to be removed. New principal is %p.", this, aTrack->GetPrincipal(), mPrincipal.get())); if (aTrack->AsVideoStreamTrack()) { nsContentUtils::CombineResourcePrincipals(&mVideoPrincipal, aTrack->GetPrincipal()); } } 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; } // Check if we became active. if (ContainsLiveTracks(mTracks)) { mActive = true; NotifyActive(); } } void DOMMediaStream::NotifyTrackRemoved(const RefPtr& 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 // playback stream in the MediaStreamGraph. It will instead be called when the // track has been confirmed removed by the graph. See BlockPlaybackTrack(). if (!mActive) { NS_ASSERTION(false, "Shouldn't remove a live track if already inactive"); return; } if (mSetInactiveOnFinish) { // For compatibility with mozCaptureStream we in some cases do not go // inactive until the playback stream finishes. return; } // Check if we became inactive. if (!ContainsLiveTracks(mTracks)) { mActive = false; NotifyInactive(); } } nsresult DOMMediaStream::DispatchTrackEvent(const nsAString& aName, const RefPtr& aTrack) { MOZ_ASSERT(aName == NS_LITERAL_STRING("addtrack"), "Only 'addtrack' is supported at this time"); MediaStreamTrackEventInit init; init.mTrack = aTrack; RefPtr event = MediaStreamTrackEvent::Constructor(this, aName, init); return DispatchTrustedEvent(event); } void DOMMediaStream::BlockPlaybackTrack(TrackPort* aTrack) { MOZ_ASSERT(aTrack); ++mTracksPendingRemoval; RefPtr> p = aTrack->BlockSourceTrackId(aTrack->GetTrack()->mTrackID, BlockingMode::CREATION); RefPtr self = this; p->Then([self] (const bool& aIgnore) { self->NotifyPlaybackTrackBlocked(); }, [] (const nsresult& aIgnore) { NS_ERROR("Could not remove track from MSG"); } ); } void DOMMediaStream::NotifyPlaybackTrackBlocked() { MOZ_ASSERT(mTracksPendingRemoval > 0, "A track reported finished blocking more times than we asked for"); if (--mTracksPendingRemoval == 0) { // The MediaStreamGraph has reported a track was blocked and we are not // waiting for any further tracks to get blocked. It is now safe to // recompute the principal based on our main thread track set state. LOG(LogLevel::Debug, ("DOMMediaStream %p saw all tracks pending removal " "finish. Recomputing principal.", this)); RecomputePrincipal(); } } DOMLocalMediaStream::~DOMLocalMediaStream() { if (mInputStream) { // Make sure Listeners of this stream know it's going away StopImpl(); } } JSObject* DOMLocalMediaStream::WrapObject(JSContext* aCx, JS::Handle aGivenProto) { return dom::LocalMediaStreamBinding::Wrap(aCx, this, aGivenProto); } void DOMLocalMediaStream::Stop() { LOG(LogLevel::Debug, ("DOMMediaStream %p Stop()", this)); nsCOMPtr pWindow = GetParentObject(); nsIDocument* document = pWindow ? pWindow->GetExtantDoc() : nullptr; nsContentUtils::ReportToConsole(nsIScriptError::warningFlag, NS_LITERAL_CSTRING("Media"), document, nsContentUtils::eDOM_PROPERTIES, "MediaStreamStopDeprecatedWarning"); StopImpl(); } void DOMLocalMediaStream::StopImpl() { if (mInputStream && mInputStream->AsSourceStream()) { mInputStream->AsSourceStream()->EndAllTrackAndFinish(); } } already_AddRefed DOMLocalMediaStream::CreateSourceStreamAsInput(nsPIDOMWindowInner* aWindow, MediaStreamGraph* aGraph, MediaStreamTrackSourceGetter* aTrackSourceGetter) { RefPtr stream = new DOMLocalMediaStream(aWindow, aTrackSourceGetter); stream->InitSourceStream(aGraph); return stream.forget(); } already_AddRefed DOMLocalMediaStream::CreateTrackUnionStreamAsInput(nsPIDOMWindowInner* aWindow, MediaStreamGraph* aGraph, MediaStreamTrackSourceGetter* aTrackSourceGetter) { RefPtr stream = new DOMLocalMediaStream(aWindow, aTrackSourceGetter); stream->InitTrackUnionStream(aGraph); return stream.forget(); } DOMAudioNodeMediaStream::DOMAudioNodeMediaStream(nsPIDOMWindowInner* aWindow, AudioNode* aNode) : DOMMediaStream(aWindow, nullptr), mStreamNode(aNode) { } DOMAudioNodeMediaStream::~DOMAudioNodeMediaStream() { } already_AddRefed DOMAudioNodeMediaStream::CreateTrackUnionStreamAsInput(nsPIDOMWindowInner* aWindow, AudioNode* aNode, MediaStreamGraph* aGraph) { RefPtr stream = new DOMAudioNodeMediaStream(aWindow, aNode); stream->InitTrackUnionStream(aGraph); return stream.forget(); } DOMHwMediaStream::DOMHwMediaStream(nsPIDOMWindowInner* aWindow) : DOMLocalMediaStream(aWindow, nullptr) { } DOMHwMediaStream::~DOMHwMediaStream() { } already_AddRefed DOMHwMediaStream::CreateHwStream(nsPIDOMWindowInner* aWindow, OverlayImage* aImage) { RefPtr stream = new DOMHwMediaStream(aWindow); MediaStreamGraph* graph = MediaStreamGraph::GetInstance(MediaStreamGraph::SYSTEM_THREAD_DRIVER, AudioChannel::Normal); stream->InitSourceStream(graph); stream->Init(stream->GetInputStream(), aImage); return stream.forget(); } void DOMHwMediaStream::Init(MediaStream* stream, OverlayImage* aImage) { SourceMediaStream* srcStream = stream->AsSourceStream(); if (srcStream) { VideoSegment segment; srcStream->AddTrack(TRACK_VIDEO_PRIMARY, 0, new VideoSegment()); srcStream->AppendToTrack(TRACK_VIDEO_PRIMARY, &segment); srcStream->AdvanceKnownTracksTime(STREAM_TIME_MAX); } } int32_t DOMHwMediaStream::RequestOverlayId() { /*** STUB ***/ return -1; } void DOMHwMediaStream::SetImageSize(uint32_t width, uint32_t height) { SourceMediaStream* srcStream = GetInputStream()->AsSourceStream(); StreamTracks::Track* track = srcStream->FindTrack(TRACK_VIDEO_PRIMARY); if (!track || !track->GetSegment()) { return; } } void DOMHwMediaStream::SetOverlayImage(OverlayImage* aImage) { if (!aImage) { return; } SourceMediaStream* srcStream = GetInputStream()->AsSourceStream(); StreamTracks::Track* track = srcStream->FindTrack(TRACK_VIDEO_PRIMARY); if (!track || !track->GetSegment()) { return; } } void DOMHwMediaStream::SetOverlayId(int32_t aOverlayId) { /*** STUB ***/ }