1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-*/
2 /* This Source Code Form is subject to the terms of the Mozilla Public
3 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
4 * You can obtain one at http://mozilla.org/MPL/2.0/. */
6 #include "DOMMediaStream.h"
8 #include "AudioCaptureTrack.h"
9 #include "AudioChannelAgent.h"
10 #include "AudioStreamTrack.h"
11 #include "MediaTrackGraph.h"
12 #include "MediaTrackGraphImpl.h"
13 #include "MediaTrackListener.h"
15 #include "VideoStreamTrack.h"
16 #include "mozilla/dom/AudioTrack.h"
17 #include "mozilla/dom/AudioTrackList.h"
18 #include "mozilla/dom/DocGroup.h"
19 #include "mozilla/dom/HTMLCanvasElement.h"
20 #include "mozilla/dom/MediaStreamBinding.h"
21 #include "mozilla/dom/MediaStreamTrackEvent.h"
22 #include "mozilla/dom/Promise.h"
23 #include "mozilla/dom/VideoTrack.h"
24 #include "mozilla/dom/VideoTrackList.h"
25 #include "mozilla/media/MediaUtils.h"
26 #include "nsContentUtils.h"
27 #include "nsGlobalWindowInner.h"
28 #include "nsIUUIDGenerator.h"
29 #include "nsPIDOMWindow.h"
30 #include "nsProxyRelease.h"
31 #include "nsRFPService.h"
32 #include "nsServiceManagerUtils.h"
38 using namespace mozilla
;
39 using namespace mozilla::dom
;
40 using namespace mozilla::layers
;
41 using namespace mozilla::media
;
43 static LazyLogModule
gMediaStreamLog("MediaStream");
44 #define LOG(type, msg) MOZ_LOG(gMediaStreamLog, type, msg)
46 static bool ContainsLiveTracks(
47 const nsTArray
<RefPtr
<MediaStreamTrack
>>& aTracks
) {
48 for (const auto& track
: aTracks
) {
49 if (track
->ReadyState() == MediaStreamTrackState::Live
) {
57 static bool ContainsLiveAudioTracks(
58 const nsTArray
<RefPtr
<MediaStreamTrack
>>& aTracks
) {
59 for (const auto& track
: aTracks
) {
60 if (track
->AsAudioStreamTrack() &&
61 track
->ReadyState() == MediaStreamTrackState::Live
) {
69 class DOMMediaStream::PlaybackTrackListener
: public MediaStreamTrackConsumer
{
71 NS_INLINE_DECL_REFCOUNTING(PlaybackTrackListener
)
73 explicit PlaybackTrackListener(DOMMediaStream
* aStream
) : mStream(aStream
) {}
75 void NotifyEnded(MediaStreamTrack
* aTrack
) override
{
85 MOZ_ASSERT(mStream
->HasTrack(*aTrack
));
86 mStream
->NotifyTrackRemoved(aTrack
);
90 virtual ~PlaybackTrackListener() = default;
92 WeakPtr
<DOMMediaStream
> mStream
;
95 NS_IMPL_CYCLE_COLLECTION_CLASS(DOMMediaStream
)
97 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(DOMMediaStream
,
100 NS_IMPL_CYCLE_COLLECTION_UNLINK(mTracks
)
101 NS_IMPL_CYCLE_COLLECTION_UNLINK(mConsumersToKeepAlive
)
102 NS_IMPL_CYCLE_COLLECTION_UNLINK_WEAK_PTR
103 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
105 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(DOMMediaStream
,
106 DOMEventTargetHelper
)
107 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mTracks
)
108 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mConsumersToKeepAlive
)
109 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
111 NS_IMPL_ADDREF_INHERITED(DOMMediaStream
, DOMEventTargetHelper
)
112 NS_IMPL_RELEASE_INHERITED(DOMMediaStream
, DOMEventTargetHelper
)
114 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(DOMMediaStream
)
115 NS_INTERFACE_MAP_ENTRY_CONCRETE(DOMMediaStream
)
116 NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper
)
118 DOMMediaStream::DOMMediaStream(nsPIDOMWindowInner
* aWindow
)
119 : DOMEventTargetHelper(aWindow
),
120 mPlaybackTrackListener(MakeAndAddRef
<PlaybackTrackListener
>(this)) {
122 nsCOMPtr
<nsIUUIDGenerator
> uuidgen
=
123 do_GetService("@mozilla.org/uuid-generator;1", &rv
);
125 if (NS_SUCCEEDED(rv
) && uuidgen
) {
127 memset(&uuid
, 0, sizeof(uuid
));
128 rv
= uuidgen
->GenerateUUIDInPlace(&uuid
);
129 if (NS_SUCCEEDED(rv
)) {
130 char buffer
[NSID_LENGTH
];
131 uuid
.ToProvidedString(buffer
);
132 mID
= NS_ConvertASCIItoUTF16(buffer
);
137 DOMMediaStream::~DOMMediaStream() { Destroy(); }
139 void DOMMediaStream::Destroy() {
140 LOG(LogLevel::Debug
, ("DOMMediaStream %p Being destroyed.", this));
141 for (const auto& track
: mTracks
) {
142 // We must remove ourselves from each track's principal change observer list
144 if (!track
->Ended()) {
145 track
->RemoveConsumer(mPlaybackTrackListener
);
148 mTrackListeners
.Clear();
151 JSObject
* DOMMediaStream::WrapObject(JSContext
* aCx
,
152 JS::Handle
<JSObject
*> aGivenProto
) {
153 return dom::MediaStream_Binding::Wrap(aCx
, this, aGivenProto
);
157 already_AddRefed
<DOMMediaStream
> DOMMediaStream::Constructor(
158 const GlobalObject
& aGlobal
, ErrorResult
& aRv
) {
159 Sequence
<OwningNonNull
<MediaStreamTrack
>> emptyTrackSeq
;
160 return Constructor(aGlobal
, emptyTrackSeq
, aRv
);
164 already_AddRefed
<DOMMediaStream
> DOMMediaStream::Constructor(
165 const GlobalObject
& aGlobal
, const DOMMediaStream
& aStream
,
167 nsTArray
<RefPtr
<MediaStreamTrack
>> tracks
;
168 aStream
.GetTracks(tracks
);
170 Sequence
<OwningNonNull
<MediaStreamTrack
>> nonNullTrackSeq
;
171 if (!nonNullTrackSeq
.SetLength(tracks
.Length(), fallible
)) {
173 aRv
.Throw(NS_ERROR_OUT_OF_MEMORY
);
177 for (size_t i
= 0; i
< tracks
.Length(); ++i
) {
178 nonNullTrackSeq
[i
] = tracks
[i
];
181 return Constructor(aGlobal
, nonNullTrackSeq
, aRv
);
185 already_AddRefed
<DOMMediaStream
> DOMMediaStream::Constructor(
186 const GlobalObject
& aGlobal
,
187 const Sequence
<OwningNonNull
<MediaStreamTrack
>>& aTracks
,
189 nsCOMPtr
<nsPIDOMWindowInner
> ownerWindow
=
190 do_QueryInterface(aGlobal
.GetAsSupports());
192 aRv
.Throw(NS_ERROR_FAILURE
);
196 auto newStream
= MakeRefPtr
<DOMMediaStream
>(ownerWindow
);
197 for (MediaStreamTrack
& track
: aTracks
) {
198 newStream
->AddTrack(track
);
200 return newStream
.forget();
203 already_AddRefed
<Promise
> DOMMediaStream::CountUnderlyingStreams(
204 const GlobalObject
& aGlobal
, ErrorResult
& aRv
) {
205 nsCOMPtr
<nsPIDOMWindowInner
> window
=
206 do_QueryInterface(aGlobal
.GetAsSupports());
208 aRv
.Throw(NS_ERROR_UNEXPECTED
);
212 nsCOMPtr
<nsIGlobalObject
> go
= do_QueryInterface(aGlobal
.GetAsSupports());
214 aRv
.Throw(NS_ERROR_UNEXPECTED
);
218 RefPtr
<Promise
> p
= Promise::Create(go
, aRv
);
223 MediaTrackGraph
* graph
= MediaTrackGraph::GetInstanceIfExists(
224 window
, MediaTrackGraph::REQUEST_DEFAULT_SAMPLE_RATE
,
225 MediaTrackGraph::DEFAULT_OUTPUT_DEVICE
);
231 auto* graphImpl
= static_cast<MediaTrackGraphImpl
*>(graph
);
233 class Counter
: public ControlMessage
{
235 Counter(MediaTrackGraphImpl
* aGraph
, const RefPtr
<Promise
>& aPromise
)
236 : ControlMessage(nullptr), mGraph(aGraph
), mPromise(aPromise
) {
237 MOZ_ASSERT(NS_IsMainThread());
240 void Run() override
{
241 TRACE("DOMMediaStream::Counter")
243 mGraph
->mTracks
.Length() + mGraph
->mSuspendedTracks
.Length();
244 mGraph
->DispatchToMainThreadStableState(NS_NewRunnableFunction(
245 "DOMMediaStream::CountUnderlyingStreams (stable state)",
246 [promise
= std::move(mPromise
), streams
]() mutable {
247 NS_DispatchToMainThread(NS_NewRunnableFunction(
248 "DOMMediaStream::CountUnderlyingStreams",
249 [promise
= std::move(promise
), streams
]() {
250 promise
->MaybeResolve(streams
);
255 // mPromise can only be AddRefed/Released on main thread.
256 // In case of shutdown, Run() does not run, so we dispatch mPromise to be
257 // released on main thread here.
258 void RunDuringShutdown() override
{
259 NS_ReleaseOnMainThread(
260 "DOMMediaStream::CountUnderlyingStreams::Counter::RunDuringShutdown",
265 // mGraph owns this Counter instance and decides its lifetime.
266 MediaTrackGraphImpl
* mGraph
;
267 RefPtr
<Promise
> mPromise
;
269 graphImpl
->AppendMessage(MakeUnique
<Counter
>(graphImpl
, p
));
274 void DOMMediaStream::GetId(nsAString
& aID
) const { aID
= mID
; }
276 void DOMMediaStream::GetAudioTracks(
277 nsTArray
<RefPtr
<AudioStreamTrack
>>& aTracks
) const {
278 for (const auto& track
: mTracks
) {
279 if (AudioStreamTrack
* t
= track
->AsAudioStreamTrack()) {
280 aTracks
.AppendElement(t
);
285 void DOMMediaStream::GetAudioTracks(
286 nsTArray
<RefPtr
<MediaStreamTrack
>>& aTracks
) const {
287 for (const auto& track
: mTracks
) {
288 if (track
->AsAudioStreamTrack()) {
289 aTracks
.AppendElement(track
);
294 void DOMMediaStream::GetVideoTracks(
295 nsTArray
<RefPtr
<VideoStreamTrack
>>& aTracks
) const {
296 for (const auto& track
: mTracks
) {
297 if (VideoStreamTrack
* t
= track
->AsVideoStreamTrack()) {
298 aTracks
.AppendElement(t
);
303 void DOMMediaStream::GetVideoTracks(
304 nsTArray
<RefPtr
<MediaStreamTrack
>>& aTracks
) const {
305 for (const auto& track
: mTracks
) {
306 if (track
->AsVideoStreamTrack()) {
307 aTracks
.AppendElement(track
);
312 void DOMMediaStream::GetTracks(
313 nsTArray
<RefPtr
<MediaStreamTrack
>>& aTracks
) const {
314 for (const auto& track
: mTracks
) {
315 aTracks
.AppendElement(track
);
319 void DOMMediaStream::AddTrack(MediaStreamTrack
& aTrack
) {
320 LOG(LogLevel::Info
, ("DOMMediaStream %p Adding track %p (from track %p)",
321 this, &aTrack
, aTrack
.GetTrack()));
323 if (HasTrack(aTrack
)) {
325 ("DOMMediaStream %p already contains track %p", this, &aTrack
));
329 mTracks
.AppendElement(&aTrack
);
331 if (!aTrack
.Ended()) {
332 NotifyTrackAdded(&aTrack
);
336 void DOMMediaStream::RemoveTrack(MediaStreamTrack
& aTrack
) {
337 if (static_cast<LogModule
*>(gMediaStreamLog
)->ShouldLog(LogLevel::Info
)) {
338 if (aTrack
.Ended()) {
340 ("DOMMediaStream %p Removing (ended) track %p", this, &aTrack
));
343 ("DOMMediaStream %p Removing track %p (from track %p)", this, &aTrack
,
348 if (!mTracks
.RemoveElement(&aTrack
)) {
350 ("DOMMediaStream %p does not contain track %p", this, &aTrack
));
354 if (!aTrack
.Ended()) {
355 NotifyTrackRemoved(&aTrack
);
359 already_AddRefed
<DOMMediaStream
> DOMMediaStream::Clone() {
360 auto newStream
= MakeRefPtr
<DOMMediaStream
>(GetOwner());
363 ("DOMMediaStream %p created clone %p", this, newStream
.get()));
365 for (const auto& track
: mTracks
) {
367 ("DOMMediaStream %p forwarding external track %p to clone %p", this,
368 track
.get(), newStream
.get()));
369 RefPtr
<MediaStreamTrack
> clone
= track
->Clone();
370 newStream
->AddTrack(*clone
);
373 return newStream
.forget();
376 bool DOMMediaStream::Active() const { return mActive
; }
377 bool DOMMediaStream::Audible() const { return mAudible
; }
379 MediaStreamTrack
* DOMMediaStream::GetTrackById(const nsAString
& aId
) const {
380 for (const auto& track
: mTracks
) {
390 bool DOMMediaStream::HasTrack(const MediaStreamTrack
& aTrack
) const {
391 return mTracks
.Contains(&aTrack
);
394 void DOMMediaStream::AddTrackInternal(MediaStreamTrack
* aTrack
) {
396 ("DOMMediaStream %p Adding owned track %p", this, aTrack
));
398 DispatchTrackEvent(u
"addtrack"_ns
, aTrack
);
401 void DOMMediaStream::RemoveTrackInternal(MediaStreamTrack
* aTrack
) {
403 ("DOMMediaStream %p Removing owned track %p", this, aTrack
));
404 if (!HasTrack(*aTrack
)) {
407 RemoveTrack(*aTrack
);
408 DispatchTrackEvent(u
"removetrack"_ns
, aTrack
);
411 already_AddRefed
<nsIPrincipal
> DOMMediaStream::GetPrincipal() {
415 nsCOMPtr
<nsIPrincipal
> principal
=
416 nsGlobalWindowInner::Cast(GetOwner())->GetPrincipal();
417 for (const auto& t
: mTracks
) {
421 nsContentUtils::CombineResourcePrincipals(&principal
, t
->GetPrincipal());
423 return principal
.forget();
426 void DOMMediaStream::NotifyActive() {
427 LOG(LogLevel::Info
, ("DOMMediaStream %p NotifyActive(). ", this));
430 for (int32_t i
= mTrackListeners
.Length() - 1; i
>= 0; --i
) {
431 mTrackListeners
[i
]->NotifyActive();
435 void DOMMediaStream::NotifyInactive() {
436 LOG(LogLevel::Info
, ("DOMMediaStream %p NotifyInactive(). ", this));
438 MOZ_ASSERT(!mActive
);
439 for (int32_t i
= mTrackListeners
.Length() - 1; i
>= 0; --i
) {
440 mTrackListeners
[i
]->NotifyInactive();
444 void DOMMediaStream::NotifyAudible() {
445 LOG(LogLevel::Info
, ("DOMMediaStream %p NotifyAudible(). ", this));
447 MOZ_ASSERT(mAudible
);
448 for (int32_t i
= mTrackListeners
.Length() - 1; i
>= 0; --i
) {
449 mTrackListeners
[i
]->NotifyAudible();
453 void DOMMediaStream::NotifyInaudible() {
454 LOG(LogLevel::Info
, ("DOMMediaStream %p NotifyInaudible(). ", this));
456 MOZ_ASSERT(!mAudible
);
457 for (int32_t i
= mTrackListeners
.Length() - 1; i
>= 0; --i
) {
458 mTrackListeners
[i
]->NotifyInaudible();
462 void DOMMediaStream::RegisterTrackListener(TrackListener
* aListener
) {
463 MOZ_ASSERT(NS_IsMainThread());
465 mTrackListeners
.AppendElement(aListener
);
468 void DOMMediaStream::UnregisterTrackListener(TrackListener
* aListener
) {
469 MOZ_ASSERT(NS_IsMainThread());
470 mTrackListeners
.RemoveElement(aListener
);
473 void DOMMediaStream::NotifyTrackAdded(const RefPtr
<MediaStreamTrack
>& aTrack
) {
474 MOZ_ASSERT(NS_IsMainThread());
476 aTrack
->AddConsumer(mPlaybackTrackListener
);
478 for (int32_t i
= mTrackListeners
.Length() - 1; i
>= 0; --i
) {
479 mTrackListeners
[i
]->NotifyTrackAdded(aTrack
);
483 // Check if we became active.
484 if (ContainsLiveTracks(mTracks
)) {
491 // Check if we became audible.
492 if (ContainsLiveAudioTracks(mTracks
)) {
499 void DOMMediaStream::NotifyTrackRemoved(
500 const RefPtr
<MediaStreamTrack
>& aTrack
) {
501 MOZ_ASSERT(NS_IsMainThread());
504 // aTrack may be null to allow HTMLMediaElement::MozCaptureStream streams
505 // to be played until the source media element has ended. The source media
506 // element will then call NotifyTrackRemoved(nullptr) to signal that we can
507 // go inactive, regardless of the timing of the last track ending.
509 aTrack
->RemoveConsumer(mPlaybackTrackListener
);
511 for (int32_t i
= mTrackListeners
.Length() - 1; i
>= 0; --i
) {
512 mTrackListeners
[i
]->NotifyTrackRemoved(aTrack
);
516 NS_ASSERTION(false, "Shouldn't remove a live track if already inactive");
522 // Check if we became inaudible.
523 if (!ContainsLiveAudioTracks(mTracks
)) {
529 // Check if we became inactive.
530 if (!ContainsLiveTracks(mTracks
)) {
536 nsresult
DOMMediaStream::DispatchTrackEvent(
537 const nsAString
& aName
, const RefPtr
<MediaStreamTrack
>& aTrack
) {
538 MediaStreamTrackEventInit init
;
539 init
.mTrack
= aTrack
;
541 RefPtr
<MediaStreamTrackEvent
> event
=
542 MediaStreamTrackEvent::Constructor(this, aName
, init
);
544 return DispatchTrustedEvent(event
);