gecko-dev/dom/media/DOMMediaStream.cpp
Andreas Pehrson d70fe4dd85 Bug 1458166 - Remove listeners before destroying MediaStreams. r=bryce
If listeners are still registered to a MediaStream on MediaStream::Destroy
(triggered by MediaStream::UnregisterUser below), they will catch and act
on further events from the MediaStream (such as a track ending).

This may dispatch runnables that are unnecessary since we know we are
shutting down.

If we first remove the listeners from the MediaStream we will never see
said events.

MozReview-Commit-ID: IZ1kENqL2C8

--HG--
extra : rebase_source : 6f9201827a30f119b4c116d0cb798858408aed20
2018-05-07 11:36:35 +02:00

1597 lines
50 KiB
C++

/* -*- 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 "AudioCaptureStream.h"
#include "AudioChannelAgent.h"
#include "AudioStreamTrack.h"
#include "Layers.h"
#include "MediaStreamGraph.h"
#include "MediaStreamGraphImpl.h"
#include "MediaStreamListener.h"
#include "VideoStreamTrack.h"
#include "mozilla/dom/AudioNode.h"
#include "mozilla/dom/AudioTrack.h"
#include "mozilla/dom/AudioTrackList.h"
#include "mozilla/dom/DocGroup.h"
#include "mozilla/dom/HTMLCanvasElement.h"
#include "mozilla/dom/LocalMediaStreamBinding.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 "nsIScriptError.h"
#include "nsIUUIDGenerator.h"
#include "nsPIDOMWindow.h"
#include "nsProxyRelease.h"
#include "nsRFPService.h"
#include "nsServiceManagerUtils.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<RefPtr<DOMMediaStream::TrackPort>>& 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<Pledge<bool>>
DOMMediaStream::TrackPort::BlockSourceTrackId(TrackID aTrackId, BlockingMode aBlockingMode)
{
if (mInputPort) {
return mInputPort->BlockSourceTrackId(aTrackId, aBlockingMode);
}
auto rejected = MakeRefPtr<Pledge<bool>>();
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(MediaStreamGraph* aGraph, 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<MediaStreamTrackSource> 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<MediaStreamTrack> newTrack =
mStream->CreateDOMTrack(aTrackID, aType, source);
aGraph->AbstractMainThread()->Dispatch(NewRunnableMethod<RefPtr<MediaStreamTrack>>(
"DOMMediaStream::AddTrackInternal",
mStream,
&DOMMediaStream::AddTrackInternal,
newTrack));
}
void DoNotifyTrackEnded(MediaStreamGraph* aGraph, MediaStream* aInputStream,
TrackID aInputTrackID, TrackID aTrackID)
{
MOZ_ASSERT(NS_IsMainThread());
if (!mStream) {
return;
}
RefPtr<MediaStreamTrack> 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()));
aGraph->AbstractMainThread()->Dispatch(
NewRunnableMethod("dom::MediaStreamTrack::OverrideEnded",
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) {
aGraph->DispatchToMainThreadAfterStreamStateUpdate(
NewRunnableMethod<MediaStreamGraph*, TrackID,
MediaSegment::Type,
RefPtr<MediaStream>,
TrackID>(
"DOMMediaStream::OwnedStreamListener::DoNotifyTrackCreated",
this,
&OwnedStreamListener::DoNotifyTrackCreated,
aGraph,
aID,
aQueuedMedia.GetType(),
aInputStream,
aInputTrackID));
} else if (aTrackEvents & TrackEventCommand::TRACK_EVENT_ENDED) {
aGraph->DispatchToMainThreadAfterStreamStateUpdate(
NewRunnableMethod<MediaStreamGraph*, RefPtr<MediaStream>, TrackID, TrackID>(
"DOMMediaStream::OwnedStreamListener::DoNotifyTrackEnded",
this,
&OwnedStreamListener::DoNotifyTrackEnded,
aGraph,
aInputStream,
aInputTrackID,
aID));
}
}
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).
MOZ_ASSERT(mStream->GetPlaybackStream());
mStream->GetPlaybackStream()->Graph()->AbstractMainThread()->Dispatch(
NewRunnableMethod("DOMMediaStream::NotifyTracksCreated",
mStream,
&DOMMediaStream::NotifyTracksCreated));
}
void DoNotifyFinished()
{
MOZ_ASSERT(NS_IsMainThread());
if (!mStream) {
return;
}
mStream->GetPlaybackStream()->Graph()->AbstractMainThread()->Dispatch(
NewRunnableMethod("DOMMediaStream::NotifyFinished",
mStream,
&DOMMediaStream::NotifyFinished));
}
// The methods below are called on the MediaStreamGraph thread.
void NotifyFinishedTrackCreation(MediaStreamGraph* aGraph) override
{
aGraph->DispatchToMainThreadAfterStreamStateUpdate(
NewRunnableMethod(
"DOMMediaStream::PlaybackStreamListener::DoNotifyFinishedTrackCreation",
this,
&PlaybackStreamListener::DoNotifyFinishedTrackCreation));
}
void NotifyEvent(MediaStreamGraph* aGraph,
MediaStreamGraphEvent event) override
{
if (event == MediaStreamGraphEvent::EVENT_FINISHED) {
aGraph->DispatchToMainThreadAfterStreamStateUpdate(
NewRunnableMethod(
"DOMMediaStream::PlaybackStreamListener::DoNotifyFinished",
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_INLINE_DECL_CYCLE_COLLECTING_NATIVE_REFCOUNTING(PlaybackTrackListener)
NS_DECL_CYCLE_COLLECTION_NATIVE_CLASS(PlaybackTrackListener)
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_CYCLE_COLLECTION_ROOT_NATIVE(DOMMediaStream::PlaybackTrackListener, AddRef)
NS_IMPL_CYCLE_COLLECTION_UNROOT_NATIVE(DOMMediaStream::PlaybackTrackListener, Release)
NS_IMPL_CYCLE_COLLECTION(DOMMediaStream::PlaybackTrackListener, 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(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(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), mSetInactiveOnFinish(false)
{
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));
if (mOwnedListener) {
if (mOwnedStream) {
mOwnedStream->RemoveListener(mOwnedListener);
}
mOwnedListener->Forget();
mOwnedListener = nullptr;
}
if (mPlaybackListener) {
if (mPlaybackStream) {
mPlaybackStream->RemoveListener(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.
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<JSObject*> aGivenProto)
{
return dom::MediaStreamBinding::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;
}
// Streams created from JS cannot have dynamically created tracks.
MediaStreamTrackSourceGetter* getter = nullptr;
RefPtr<DOMMediaStream> 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, ownerWindow,
MediaStreamGraph::REQUEST_DEFAULT_SAMPLE_RATE);
newStream->InitPlaybackStreamCommon(graph);
}
return newStream.forget();
}
double
DOMMediaStream::CurrentTime()
{
if (!mPlaybackStream) {
return 0.0;
}
// The value of a MediaStream's CurrentTime will always advance forward; it will never
// reset (even if one rewinds a video.) Therefore we can use a single Random Seed
// initialized at the same time as the object.
return nsRFPService::ReduceTimePrecisionAsSecs(mPlaybackStream->
StreamTimeToSeconds(mPlaybackStream->GetCurrentTime() - mLogicalStreamStartTime),
GetRandomTimelineSeed());
}
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;
}
MediaStreamGraph* graph = MediaStreamGraph::GetInstanceIfExists(window,
MediaStreamGraph::REQUEST_DEFAULT_SAMPLE_RATE);
if (!graph) {
p->MaybeResolve(0);
return p.forget();
}
auto* graphImpl = static_cast<MediaStreamGraphImpl*>(graph);
class Counter : public ControlMessage
{
public:
Counter(MediaStreamGraphImpl* aGraph,
const RefPtr<Promise>& aPromise)
: ControlMessage(nullptr)
, mGraph(aGraph)
, mPromise(MakeAndAddRef<nsMainThreadPtrHolder<Promise>>("DOMMediaStream::Counter::mPromise", aPromise))
{
MOZ_ASSERT(NS_IsMainThread());
}
void Run() override
{
nsMainThreadPtrHandle<Promise>& promise = mPromise;
uint32_t streams = mGraph->mStreams.Length() +
mGraph->mSuspendedStreams.Length();
mGraph->DispatchToMainThreadAfterStreamStateUpdate(
NewRunnableFrom([promise, streams]() mutable {
promise->MaybeResolve(streams);
return NS_OK;
}));
}
private:
// mGraph owns this Counter instance and decides its lifetime.
MediaStreamGraphImpl* mGraph;
nsMainThreadPtrHandle<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 RefPtr<TrackPort>& info : mTracks) {
AudioStreamTrack* t = info->GetTrack()->AsAudioStreamTrack();
if (t) {
aTracks.AppendElement(t);
}
}
}
void
DOMMediaStream::GetVideoTracks(nsTArray<RefPtr<VideoStreamTrack> >& aTracks) const
{
for (const RefPtr<TrackPort>& info : mTracks) {
VideoStreamTrack* t = info->GetTrack()->AsVideoStreamTrack();
if (t) {
aTracks.AppendElement(t);
}
}
}
void
DOMMediaStream::GetTracks(nsTArray<RefPtr<MediaStreamTrack> >& aTracks) const
{
for (const RefPtr<TrackPort>& info : mTracks) {
aTracks.AppendElement(info->GetTrack());
}
}
void
DOMMediaStream::AddTrack(MediaStreamTrack& aTrack)
{
MOZ_RELEASE_ASSERT(mPlaybackStream);
RefPtr<ProcessedMediaStream> 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<nsPIDOMWindowInner> 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<MediaInputPort> inputPort =
GetPlaybackStream()->AllocateInputPort(aTrack.GetOwnedStream(),
aTrack.mTrackID);
RefPtr<TrackPort> 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<TrackPort> toRemove = FindPlaybackTrackPort(aTrack);
if (!toRemove) {
LOG(LogLevel::Debug, ("DOMMediaStream %p does not contain track %p", this, &aTrack));
return;
}
DebugOnly<bool> 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<MediaStreamTrackSource>
GetMediaStreamTrackSource(TrackID aInputTrackID) override
{
MediaStreamTrack* sourceTrack =
mStream->FindOwnedDOMTrack(mStream->GetOwnedStream(), aInputTrackID);
MOZ_RELEASE_ASSERT(sourceTrack);
return do_AddRef(&sourceTrack->GetSource());
}
protected:
virtual ~ClonedStreamSourceGetter() {}
RefPtr<DOMMediaStream> mStream;
};
NS_IMPL_ADDREF_INHERITED(ClonedStreamSourceGetter,
MediaStreamTrackSourceGetter)
NS_IMPL_RELEASE_INHERITED(ClonedStreamSourceGetter,
MediaStreamTrackSourceGetter)
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(ClonedStreamSourceGetter)
NS_INTERFACE_MAP_END_INHERITING(MediaStreamTrackSourceGetter)
NS_IMPL_CYCLE_COLLECTION_INHERITED(ClonedStreamSourceGetter,
MediaStreamTrackSourceGetter,
mStream)
already_AddRefed<DOMMediaStream>
DOMMediaStream::Clone()
{
return CloneInternal(TrackForwardingOption::CURRENT);
}
already_AddRefed<DOMMediaStream>
DOMMediaStream::CloneInternal(TrackForwardingOption aForwarding)
{
RefPtr<DOMMediaStream> 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<TrackPort>& info : mTracks) {
MediaStreamTrack& track = *info->GetTrack();
LOG(LogLevel::Debug, ("DOMMediaStream %p forwarding external track %p to clone %p",
this, &track, newStream.get()));
RefPtr<MediaStreamTrack> 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<TrackID> tracksToBlock;
for (const RefPtr<TrackPort>& 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<TrackPort>& 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<TrackPort>& 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::IsFinished() const
{
return !mPlaybackStream || mPlaybackStream->IsFinished();
}
TrackRate
DOMMediaStream::GraphRate()
{
if (mPlaybackStream) { return mPlaybackStream->GraphRate(); }
if (mOwnedStream) { return mOwnedStream->GraphRate(); }
if (mInputStream) { return mInputStream->GraphRate(); }
MOZ_ASSERT(false, "Not hooked up to a graph");
return 0;
}
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<BasicTrackSource> audioCaptureSource =
new BasicTrackSource(aPrincipal, MediaSourceEnum::AudioCapture);
AudioCaptureStream* audioCaptureStream =
static_cast<AudioCaptureStream*>(aGraph->CreateAudioCaptureStream(AUDIO_TRACK));
InitInputStreamCommon(audioCaptureStream, aGraph);
InitOwnedStreamCommon(aGraph);
InitPlaybackStreamCommon(aGraph);
RefPtr<MediaStreamTrack> 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->QueueSetAutofinish(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->QueueSetAutofinish(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>
DOMMediaStream::CreateSourceStreamAsInput(nsPIDOMWindowInner* aWindow,
MediaStreamGraph* aGraph,
MediaStreamTrackSourceGetter* aTrackSourceGetter)
{
RefPtr<DOMMediaStream> stream = new DOMMediaStream(aWindow, aTrackSourceGetter);
stream->InitSourceStream(aGraph);
return stream.forget();
}
already_AddRefed<DOMMediaStream>
DOMMediaStream::CreateTrackUnionStreamAsInput(nsPIDOMWindowInner* aWindow,
MediaStreamGraph* aGraph,
MediaStreamTrackSourceGetter* aTrackSourceGetter)
{
RefPtr<DOMMediaStream> stream = new DOMMediaStream(aWindow, aTrackSourceGetter);
stream->InitTrackUnionStream(aGraph);
return stream.forget();
}
already_AddRefed<DOMMediaStream>
DOMMediaStream::CreateAudioCaptureStreamAsInput(nsPIDOMWindowInner* aWindow,
nsIPrincipal* aPrincipal,
MediaStreamGraph* aGraph)
{
// Audio capture doesn't create tracks dynamically
MediaStreamTrackSourceGetter* getter = nullptr;
RefPtr<DOMMediaStream> 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<nsIPrincipal> previousPrincipal = mPrincipal.forget();
nsCOMPtr<nsIPrincipal> 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<TrackPort>& 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<DOMMediaStream>* aObserver)
{
return mPrincipalChangeObservers.AppendElement(aObserver) != nullptr;
}
bool
DOMMediaStream::RemovePrincipalChangeObserver(
PrincipalChangeObserver<DOMMediaStream>* 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<MediaStreamTrack>
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<MediaStreamTrack> 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<MediaStreamTrack>
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<MediaStreamTrack> 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<MediaInputPort> 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->SetMuted(aTrack.Muted());
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<Pledge<bool, nsresult>> blockingPledge =
inputPort->BlockSourceTrackId(inputTrackID,
BlockingMode::END_EXISTING);
Unused << blockingPledge;
}
return newTrack.forget();
}
static DOMMediaStream::TrackPort*
FindTrackPortAmongTracks(const MediaStreamTrack& aTrack,
const nsTArray<RefPtr<DOMMediaStream::TrackPort>>& aTracks)
{
for (const RefPtr<DOMMediaStream::TrackPort>& 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<TrackPort>& 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<TrackPort>& 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<nsAutoPtr<OnTracksAvailableCallback> > 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<MediaStreamTrack>& 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<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
// 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<MediaStreamTrack>& aTrack)
{
MediaStreamTrackEventInit init;
init.mTrack = aTrack;
RefPtr<MediaStreamTrackEvent> event =
MediaStreamTrackEvent::Constructor(this, aName, init);
return DispatchTrustedEvent(event);
}
void
DOMMediaStream::BlockPlaybackTrack(TrackPort* aTrack)
{
MOZ_ASSERT(aTrack);
++mTracksPendingRemoval;
RefPtr<Pledge<bool>> p =
aTrack->BlockSourceTrackId(aTrack->GetTrack()->mTrackID,
BlockingMode::CREATION);
RefPtr<DOMMediaStream> 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<JSObject*> aGivenProto)
{
return dom::LocalMediaStreamBinding::Wrap(aCx, this, aGivenProto);
}
void
DOMLocalMediaStream::Stop()
{
LOG(LogLevel::Debug, ("DOMMediaStream %p Stop()", this));
nsCOMPtr<nsPIDOMWindowInner> 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>
DOMLocalMediaStream::CreateSourceStreamAsInput(nsPIDOMWindowInner* aWindow,
MediaStreamGraph* aGraph,
MediaStreamTrackSourceGetter* aTrackSourceGetter)
{
RefPtr<DOMLocalMediaStream> stream =
new DOMLocalMediaStream(aWindow, aTrackSourceGetter);
stream->InitSourceStream(aGraph);
return stream.forget();
}
already_AddRefed<DOMLocalMediaStream>
DOMLocalMediaStream::CreateTrackUnionStreamAsInput(nsPIDOMWindowInner* aWindow,
MediaStreamGraph* aGraph,
MediaStreamTrackSourceGetter* aTrackSourceGetter)
{
RefPtr<DOMLocalMediaStream> 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>
DOMAudioNodeMediaStream::CreateTrackUnionStreamAsInput(nsPIDOMWindowInner* aWindow,
AudioNode* aNode,
MediaStreamGraph* aGraph)
{
RefPtr<DOMAudioNodeMediaStream> stream = new DOMAudioNodeMediaStream(aWindow, aNode);
stream->InitTrackUnionStream(aGraph);
return stream.forget();
}