diff options
Diffstat (limited to '')
-rw-r--r-- | dom/media/DOMMediaStream.cpp | 537 |
1 files changed, 537 insertions, 0 deletions
diff --git a/dom/media/DOMMediaStream.cpp b/dom/media/DOMMediaStream.cpp new file mode 100644 index 0000000000..81f7019e3a --- /dev/null +++ b/dom/media/DOMMediaStream.cpp @@ -0,0 +1,537 @@ +/* -*- 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 "AudioCaptureTrack.h" +#include "AudioChannelAgent.h" +#include "AudioStreamTrack.h" +#include "MediaTrackGraph.h" +#include "MediaTrackGraphImpl.h" +#include "MediaTrackListener.h" +#include "Tracing.h" +#include "VideoStreamTrack.h" +#include "mozilla/dom/AudioTrack.h" +#include "mozilla/dom/AudioTrackList.h" +#include "mozilla/dom/DocGroup.h" +#include "mozilla/dom/HTMLCanvasElement.h" +#include "mozilla/dom/MediaStreamBinding.h" +#include "mozilla/dom/MediaStreamTrackEvent.h" +#include "mozilla/dom/Promise.h" +#include "mozilla/dom/VideoTrack.h" +#include "mozilla/dom/VideoTrackList.h" +#include "mozilla/media/MediaUtils.h" +#include "nsContentUtils.h" +#include "nsGlobalWindowInner.h" +#include "nsIUUIDGenerator.h" +#include "nsPIDOMWindow.h" +#include "nsProxyRelease.h" +#include "nsRFPService.h" +#include "nsServiceManagerUtils.h" + +#ifdef LOG +# undef LOG +#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) + +static bool ContainsLiveTracks( + const nsTArray<RefPtr<MediaStreamTrack>>& aTracks) { + for (const auto& track : aTracks) { + if (track->ReadyState() == MediaStreamTrackState::Live) { + return true; + } + } + + return false; +} + +static bool ContainsLiveAudioTracks( + const nsTArray<RefPtr<MediaStreamTrack>>& aTracks) { + for (const auto& track : aTracks) { + if (track->AsAudioStreamTrack() && + track->ReadyState() == MediaStreamTrackState::Live) { + return true; + } + } + + return false; +} + +class DOMMediaStream::PlaybackTrackListener : public MediaStreamTrackConsumer { + public: + NS_INLINE_DECL_REFCOUNTING(PlaybackTrackListener) + + explicit PlaybackTrackListener(DOMMediaStream* aStream) : mStream(aStream) {} + + void NotifyEnded(MediaStreamTrack* aTrack) override { + if (!mStream) { + return; + } + + if (!aTrack) { + MOZ_ASSERT(false); + return; + } + + MOZ_ASSERT(mStream->HasTrack(*aTrack)); + mStream->NotifyTrackRemoved(aTrack); + } + + protected: + virtual ~PlaybackTrackListener() = default; + + WeakPtr<DOMMediaStream> mStream; +}; + +NS_IMPL_CYCLE_COLLECTION_CLASS(DOMMediaStream) + +NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(DOMMediaStream, + DOMEventTargetHelper) + tmp->Destroy(); + NS_IMPL_CYCLE_COLLECTION_UNLINK(mTracks) + NS_IMPL_CYCLE_COLLECTION_UNLINK(mConsumersToKeepAlive) + NS_IMPL_CYCLE_COLLECTION_UNLINK_WEAK_PTR +NS_IMPL_CYCLE_COLLECTION_UNLINK_END + +NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(DOMMediaStream, + DOMEventTargetHelper) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mTracks) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mConsumersToKeepAlive) +NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END + +NS_IMPL_ADDREF_INHERITED(DOMMediaStream, DOMEventTargetHelper) +NS_IMPL_RELEASE_INHERITED(DOMMediaStream, DOMEventTargetHelper) + +NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(DOMMediaStream) + NS_INTERFACE_MAP_ENTRY_CONCRETE(DOMMediaStream) +NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper) + +DOMMediaStream::DOMMediaStream(nsPIDOMWindowInner* aWindow) + : DOMEventTargetHelper(aWindow), + mPlaybackTrackListener(MakeAndAddRef<PlaybackTrackListener>(this)) { + nsresult rv; + nsCOMPtr<nsIUUIDGenerator> 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)); + for (const auto& track : mTracks) { + // We must remove ourselves from each track's principal change observer list + // before we die. + if (!track->Ended()) { + track->RemoveConsumer(mPlaybackTrackListener); + } + } + mTrackListeners.Clear(); +} + +JSObject* DOMMediaStream::WrapObject(JSContext* aCx, + JS::Handle<JSObject*> aGivenProto) { + return dom::MediaStream_Binding::Wrap(aCx, this, aGivenProto); +} + +/* static */ +already_AddRefed<DOMMediaStream> DOMMediaStream::Constructor( + const GlobalObject& aGlobal, ErrorResult& aRv) { + Sequence<OwningNonNull<MediaStreamTrack>> emptyTrackSeq; + return Constructor(aGlobal, emptyTrackSeq, aRv); +} + +/* static */ +already_AddRefed<DOMMediaStream> DOMMediaStream::Constructor( + const GlobalObject& aGlobal, const DOMMediaStream& aStream, + ErrorResult& aRv) { + nsTArray<RefPtr<MediaStreamTrack>> tracks; + aStream.GetTracks(tracks); + + Sequence<OwningNonNull<MediaStreamTrack>> 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> DOMMediaStream::Constructor( + const GlobalObject& aGlobal, + const Sequence<OwningNonNull<MediaStreamTrack>>& aTracks, + ErrorResult& aRv) { + nsCOMPtr<nsPIDOMWindowInner> ownerWindow = + do_QueryInterface(aGlobal.GetAsSupports()); + if (!ownerWindow) { + aRv.Throw(NS_ERROR_FAILURE); + return nullptr; + } + + auto newStream = MakeRefPtr<DOMMediaStream>(ownerWindow); + for (MediaStreamTrack& track : aTracks) { + newStream->AddTrack(track); + } + return newStream.forget(); +} + +already_AddRefed<Promise> DOMMediaStream::CountUnderlyingStreams( + const GlobalObject& aGlobal, ErrorResult& aRv) { + nsCOMPtr<nsPIDOMWindowInner> window = + do_QueryInterface(aGlobal.GetAsSupports()); + if (!window) { + aRv.Throw(NS_ERROR_UNEXPECTED); + return nullptr; + } + + nsCOMPtr<nsIGlobalObject> go = do_QueryInterface(aGlobal.GetAsSupports()); + if (!go) { + aRv.Throw(NS_ERROR_UNEXPECTED); + return nullptr; + } + + RefPtr<Promise> p = Promise::Create(go, aRv); + if (aRv.Failed()) { + return nullptr; + } + + MediaTrackGraph* graph = MediaTrackGraph::GetInstanceIfExists( + window, MediaTrackGraph::REQUEST_DEFAULT_SAMPLE_RATE, + MediaTrackGraph::DEFAULT_OUTPUT_DEVICE); + if (!graph) { + p->MaybeResolve(0); + return p.forget(); + } + + auto* graphImpl = static_cast<MediaTrackGraphImpl*>(graph); + + class Counter : public ControlMessage { + public: + Counter(MediaTrackGraphImpl* aGraph, const RefPtr<Promise>& aPromise) + : ControlMessage(nullptr), mGraph(aGraph), mPromise(aPromise) { + MOZ_ASSERT(NS_IsMainThread()); + } + + void Run() override { + TRACE("DOMMediaStream::Counter") + uint32_t streams = + mGraph->mTracks.Length() + mGraph->mSuspendedTracks.Length(); + mGraph->DispatchToMainThreadStableState(NS_NewRunnableFunction( + "DOMMediaStream::CountUnderlyingStreams (stable state)", + [promise = std::move(mPromise), streams]() mutable { + NS_DispatchToMainThread(NS_NewRunnableFunction( + "DOMMediaStream::CountUnderlyingStreams", + [promise = std::move(promise), streams]() { + promise->MaybeResolve(streams); + })); + })); + } + + // mPromise can only be AddRefed/Released on main thread. + // In case of shutdown, Run() does not run, so we dispatch mPromise to be + // released on main thread here. + void RunDuringShutdown() override { + NS_ReleaseOnMainThread( + "DOMMediaStream::CountUnderlyingStreams::Counter::RunDuringShutdown", + mPromise.forget()); + } + + private: + // mGraph owns this Counter instance and decides its lifetime. + MediaTrackGraphImpl* mGraph; + RefPtr<Promise> mPromise; + }; + graphImpl->AppendMessage(MakeUnique<Counter>(graphImpl, p)); + + return p.forget(); +} + +void DOMMediaStream::GetId(nsAString& aID) const { aID = mID; } + +void DOMMediaStream::GetAudioTracks( + nsTArray<RefPtr<AudioStreamTrack>>& aTracks) const { + for (const auto& track : mTracks) { + if (AudioStreamTrack* t = track->AsAudioStreamTrack()) { + aTracks.AppendElement(t); + } + } +} + +void DOMMediaStream::GetAudioTracks( + nsTArray<RefPtr<MediaStreamTrack>>& aTracks) const { + for (const auto& track : mTracks) { + if (track->AsAudioStreamTrack()) { + aTracks.AppendElement(track); + } + } +} + +void DOMMediaStream::GetVideoTracks( + nsTArray<RefPtr<VideoStreamTrack>>& aTracks) const { + for (const auto& track : mTracks) { + if (VideoStreamTrack* t = track->AsVideoStreamTrack()) { + aTracks.AppendElement(t); + } + } +} + +void DOMMediaStream::GetVideoTracks( + nsTArray<RefPtr<MediaStreamTrack>>& aTracks) const { + for (const auto& track : mTracks) { + if (track->AsVideoStreamTrack()) { + aTracks.AppendElement(track); + } + } +} + +void DOMMediaStream::GetTracks( + nsTArray<RefPtr<MediaStreamTrack>>& aTracks) const { + for (const auto& track : mTracks) { + aTracks.AppendElement(track); + } +} + +void DOMMediaStream::AddTrack(MediaStreamTrack& aTrack) { + LOG(LogLevel::Info, ("DOMMediaStream %p Adding track %p (from track %p)", + this, &aTrack, aTrack.GetTrack())); + + if (HasTrack(aTrack)) { + LOG(LogLevel::Debug, + ("DOMMediaStream %p already contains track %p", this, &aTrack)); + return; + } + + mTracks.AppendElement(&aTrack); + + if (!aTrack.Ended()) { + NotifyTrackAdded(&aTrack); + } +} + +void DOMMediaStream::RemoveTrack(MediaStreamTrack& aTrack) { + LOG(LogLevel::Info, ("DOMMediaStream %p Removing track %p (from track %p)", + this, &aTrack, aTrack.GetTrack())); + + if (!mTracks.RemoveElement(&aTrack)) { + LOG(LogLevel::Debug, + ("DOMMediaStream %p does not contain track %p", this, &aTrack)); + return; + } + + if (!aTrack.Ended()) { + NotifyTrackRemoved(&aTrack); + } +} + +already_AddRefed<DOMMediaStream> DOMMediaStream::Clone() { + auto newStream = MakeRefPtr<DOMMediaStream>(GetOwner()); + + LOG(LogLevel::Info, + ("DOMMediaStream %p created clone %p", this, newStream.get())); + + for (const auto& track : mTracks) { + LOG(LogLevel::Debug, + ("DOMMediaStream %p forwarding external track %p to clone %p", this, + track.get(), newStream.get())); + RefPtr<MediaStreamTrack> clone = track->Clone(); + newStream->AddTrack(*clone); + } + + return newStream.forget(); +} + +bool DOMMediaStream::Active() const { return mActive; } +bool DOMMediaStream::Audible() const { return mAudible; } + +MediaStreamTrack* DOMMediaStream::GetTrackById(const nsAString& aId) const { + for (const auto& track : mTracks) { + nsString id; + track->GetId(id); + if (id == aId) { + return track; + } + } + return nullptr; +} + +bool DOMMediaStream::HasTrack(const MediaStreamTrack& aTrack) const { + return mTracks.Contains(&aTrack); +} + +void DOMMediaStream::AddTrackInternal(MediaStreamTrack* aTrack) { + LOG(LogLevel::Debug, + ("DOMMediaStream %p Adding owned track %p", this, aTrack)); + AddTrack(*aTrack); + DispatchTrackEvent(u"addtrack"_ns, aTrack); +} + +void DOMMediaStream::RemoveTrackInternal(MediaStreamTrack* aTrack) { + LOG(LogLevel::Debug, + ("DOMMediaStream %p Removing owned track %p", this, aTrack)); + if (!HasTrack(*aTrack)) { + return; + } + RemoveTrack(*aTrack); + DispatchTrackEvent(u"removetrack"_ns, aTrack); +} + +already_AddRefed<nsIPrincipal> DOMMediaStream::GetPrincipal() { + if (!GetOwner()) { + return nullptr; + } + nsCOMPtr<nsIPrincipal> principal = + nsGlobalWindowInner::Cast(GetOwner())->GetPrincipal(); + for (const auto& t : mTracks) { + if (t->Ended()) { + continue; + } + nsContentUtils::CombineResourcePrincipals(&principal, t->GetPrincipal()); + } + return principal.forget(); +} + +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::NotifyAudible() { + LOG(LogLevel::Info, ("DOMMediaStream %p NotifyAudible(). ", this)); + + MOZ_ASSERT(mAudible); + for (int32_t i = mTrackListeners.Length() - 1; i >= 0; --i) { + mTrackListeners[i]->NotifyAudible(); + } +} + +void DOMMediaStream::NotifyInaudible() { + LOG(LogLevel::Info, ("DOMMediaStream %p NotifyInaudible(). ", this)); + + MOZ_ASSERT(!mAudible); + for (int32_t i = mTrackListeners.Length() - 1; i >= 0; --i) { + mTrackListeners[i]->NotifyInaudible(); + } +} + +void DOMMediaStream::RegisterTrackListener(TrackListener* aListener) { + MOZ_ASSERT(NS_IsMainThread()); + + mTrackListeners.AppendElement(aListener); +} + +void DOMMediaStream::UnregisterTrackListener(TrackListener* aListener) { + MOZ_ASSERT(NS_IsMainThread()); + mTrackListeners.RemoveElement(aListener); +} + +void DOMMediaStream::NotifyTrackAdded(const RefPtr<MediaStreamTrack>& aTrack) { + MOZ_ASSERT(NS_IsMainThread()); + + aTrack->AddConsumer(mPlaybackTrackListener); + + for (int32_t i = mTrackListeners.Length() - 1; i >= 0; --i) { + mTrackListeners[i]->NotifyTrackAdded(aTrack); + } + + if (!mActive) { + // Check if we became active. + if (ContainsLiveTracks(mTracks)) { + mActive = true; + NotifyActive(); + } + } + + if (!mAudible) { + // Check if we became audible. + if (ContainsLiveAudioTracks(mTracks)) { + mAudible = true; + NotifyAudible(); + } + } +} + +void DOMMediaStream::NotifyTrackRemoved( + const RefPtr<MediaStreamTrack>& aTrack) { + MOZ_ASSERT(NS_IsMainThread()); + + if (aTrack) { + // aTrack may be null to allow HTMLMediaElement::MozCaptureStream streams + // to be played until the source media element has ended. The source media + // element will then call NotifyTrackRemoved(nullptr) to signal that we can + // go inactive, regardless of the timing of the last track ending. + + aTrack->RemoveConsumer(mPlaybackTrackListener); + + for (int32_t i = mTrackListeners.Length() - 1; i >= 0; --i) { + mTrackListeners[i]->NotifyTrackRemoved(aTrack); + } + + if (!mActive) { + NS_ASSERTION(false, "Shouldn't remove a live track if already inactive"); + return; + } + } + + if (mAudible) { + // Check if we became inaudible. + if (!ContainsLiveAudioTracks(mTracks)) { + mAudible = false; + NotifyInaudible(); + } + } + + // Check if we became inactive. + if (!ContainsLiveTracks(mTracks)) { + mActive = false; + NotifyInactive(); + } +} + +nsresult DOMMediaStream::DispatchTrackEvent( + const nsAString& aName, const RefPtr<MediaStreamTrack>& aTrack) { + MediaStreamTrackEventInit init; + init.mTrack = aTrack; + + RefPtr<MediaStreamTrackEvent> event = + MediaStreamTrackEvent::Constructor(this, aName, init); + + return DispatchTrustedEvent(event); +} |