DOMMediaStream.cpp (16083B)
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/. */ 5 6 #include "DOMMediaStream.h" 7 8 #include "AudioCaptureTrack.h" 9 #include "AudioChannelAgent.h" 10 #include "AudioStreamTrack.h" 11 #include "MediaTrackGraph.h" 12 #include "MediaTrackGraphImpl.h" 13 #include "MediaTrackListener.h" 14 #include "Tracing.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" 33 34 #ifdef LOG 35 # undef LOG 36 #endif 37 38 using namespace mozilla; 39 using namespace mozilla::dom; 40 using namespace mozilla::layers; 41 using namespace mozilla::media; 42 43 static LazyLogModule gMediaStreamLog("MediaStream"); 44 #define LOG(type, msg) MOZ_LOG(gMediaStreamLog, type, msg) 45 46 static bool ContainsLiveTracks( 47 const nsTArray<RefPtr<MediaStreamTrack>>& aTracks) { 48 for (const auto& track : aTracks) { 49 if (track->ReadyState() == MediaStreamTrackState::Live) { 50 return true; 51 } 52 } 53 54 return false; 55 } 56 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) { 62 return true; 63 } 64 } 65 66 return false; 67 } 68 69 class DOMMediaStream::PlaybackTrackListener : public MediaStreamTrackConsumer { 70 public: 71 NS_INLINE_DECL_REFCOUNTING(PlaybackTrackListener) 72 73 explicit PlaybackTrackListener(DOMMediaStream* aStream) : mStream(aStream) {} 74 75 void NotifyEnded(MediaStreamTrack* aTrack) override { 76 if (!mStream) { 77 return; 78 } 79 80 if (!aTrack) { 81 MOZ_ASSERT(false); 82 return; 83 } 84 85 MOZ_ASSERT(mStream->HasTrack(*aTrack)); 86 mStream->NotifyTrackRemoved(aTrack); 87 } 88 89 protected: 90 virtual ~PlaybackTrackListener() = default; 91 92 WeakPtr<DOMMediaStream> mStream; 93 }; 94 95 NS_IMPL_CYCLE_COLLECTION_CLASS(DOMMediaStream) 96 97 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(DOMMediaStream, 98 DOMEventTargetHelper) 99 tmp->Destroy(); 100 NS_IMPL_CYCLE_COLLECTION_UNLINK(mTracks) 101 NS_IMPL_CYCLE_COLLECTION_UNLINK(mConsumersToKeepAlive) 102 NS_IMPL_CYCLE_COLLECTION_UNLINK(mTrackListeners) 103 NS_IMPL_CYCLE_COLLECTION_UNLINK_WEAK_PTR 104 NS_IMPL_CYCLE_COLLECTION_UNLINK_END 105 106 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(DOMMediaStream, 107 DOMEventTargetHelper) 108 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mTracks) 109 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mConsumersToKeepAlive) 110 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mTrackListeners) 111 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END 112 113 NS_IMPL_ADDREF_INHERITED(DOMMediaStream, DOMEventTargetHelper) 114 NS_IMPL_RELEASE_INHERITED(DOMMediaStream, DOMEventTargetHelper) 115 116 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(DOMMediaStream) 117 NS_INTERFACE_MAP_ENTRY_CONCRETE(DOMMediaStream) 118 NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper) 119 120 NS_IMPL_CYCLE_COLLECTION(DOMMediaStream::TrackListener) 121 NS_IMPL_CYCLE_COLLECTING_ADDREF(DOMMediaStream::TrackListener) 122 NS_IMPL_CYCLE_COLLECTING_RELEASE(DOMMediaStream::TrackListener) 123 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(DOMMediaStream::TrackListener) 124 NS_INTERFACE_MAP_ENTRY(nsISupports) 125 NS_INTERFACE_MAP_END 126 127 DOMMediaStream::DOMMediaStream(nsPIDOMWindowInner* aWindow) 128 : DOMEventTargetHelper(aWindow), 129 mPlaybackTrackListener(MakeAndAddRef<PlaybackTrackListener>(this)) { 130 nsresult rv; 131 nsCOMPtr<nsIUUIDGenerator> uuidgen = 132 do_GetService("@mozilla.org/uuid-generator;1", &rv); 133 134 if (NS_SUCCEEDED(rv) && uuidgen) { 135 nsID uuid; 136 memset(&uuid, 0, sizeof(uuid)); 137 rv = uuidgen->GenerateUUIDInPlace(&uuid); 138 if (NS_SUCCEEDED(rv)) { 139 char buffer[NSID_LENGTH]; 140 uuid.ToProvidedString(buffer); 141 mID = NS_ConvertASCIItoUTF16(buffer); 142 } 143 } 144 } 145 146 DOMMediaStream::~DOMMediaStream() { Destroy(); } 147 148 void DOMMediaStream::Destroy() { 149 LOG(LogLevel::Debug, ("DOMMediaStream %p Being destroyed.", this)); 150 for (const auto& track : mTracks) { 151 // We must remove ourselves from each track's principal change observer list 152 // before we die. 153 if (!track->Ended()) { 154 track->RemoveConsumer(mPlaybackTrackListener); 155 } 156 } 157 mTrackListeners.Clear(); 158 } 159 160 JSObject* DOMMediaStream::WrapObject(JSContext* aCx, 161 JS::Handle<JSObject*> aGivenProto) { 162 return dom::MediaStream_Binding::Wrap(aCx, this, aGivenProto); 163 } 164 165 /* static */ 166 already_AddRefed<DOMMediaStream> DOMMediaStream::Constructor( 167 const GlobalObject& aGlobal, ErrorResult& aRv) { 168 Sequence<OwningNonNull<MediaStreamTrack>> emptyTrackSeq; 169 return Constructor(aGlobal, emptyTrackSeq, aRv); 170 } 171 172 /* static */ 173 already_AddRefed<DOMMediaStream> DOMMediaStream::Constructor( 174 const GlobalObject& aGlobal, const DOMMediaStream& aStream, 175 ErrorResult& aRv) { 176 nsTArray<RefPtr<MediaStreamTrack>> tracks; 177 aStream.GetTracks(tracks); 178 179 Sequence<OwningNonNull<MediaStreamTrack>> nonNullTrackSeq; 180 if (!nonNullTrackSeq.SetLength(tracks.Length(), fallible)) { 181 MOZ_ASSERT(false); 182 aRv.Throw(NS_ERROR_OUT_OF_MEMORY); 183 return nullptr; 184 } 185 186 for (size_t i = 0; i < tracks.Length(); ++i) { 187 nonNullTrackSeq[i] = tracks[i]; 188 } 189 190 return Constructor(aGlobal, nonNullTrackSeq, aRv); 191 } 192 193 /* static */ 194 already_AddRefed<DOMMediaStream> DOMMediaStream::Constructor( 195 const GlobalObject& aGlobal, 196 const Sequence<OwningNonNull<MediaStreamTrack>>& aTracks, 197 ErrorResult& aRv) { 198 nsCOMPtr<nsPIDOMWindowInner> ownerWindow = 199 do_QueryInterface(aGlobal.GetAsSupports()); 200 if (!ownerWindow) { 201 aRv.Throw(NS_ERROR_FAILURE); 202 return nullptr; 203 } 204 205 auto newStream = MakeRefPtr<DOMMediaStream>(ownerWindow); 206 for (MediaStreamTrack& track : aTracks) { 207 newStream->AddTrack(track); 208 } 209 return newStream.forget(); 210 } 211 212 already_AddRefed<Promise> DOMMediaStream::CountUnderlyingStreams( 213 const GlobalObject& aGlobal, ErrorResult& aRv) { 214 nsCOMPtr<nsPIDOMWindowInner> window = 215 do_QueryInterface(aGlobal.GetAsSupports()); 216 if (!window) { 217 aRv.Throw(NS_ERROR_UNEXPECTED); 218 return nullptr; 219 } 220 221 nsCOMPtr<nsIGlobalObject> go = do_QueryInterface(aGlobal.GetAsSupports()); 222 if (!go) { 223 aRv.Throw(NS_ERROR_UNEXPECTED); 224 return nullptr; 225 } 226 227 RefPtr<Promise> p = Promise::Create(go, aRv); 228 if (aRv.Failed()) { 229 return nullptr; 230 } 231 232 MediaTrackGraph* graph = MediaTrackGraph::GetInstanceIfExists( 233 window, MediaTrackGraph::REQUEST_DEFAULT_SAMPLE_RATE, 234 MediaTrackGraph::DEFAULT_OUTPUT_DEVICE); 235 if (!graph) { 236 p->MaybeResolve(0); 237 return p.forget(); 238 } 239 240 auto* graphImpl = static_cast<MediaTrackGraphImpl*>(graph); 241 242 class Counter : public ControlMessage { 243 public: 244 Counter(MediaTrackGraphImpl* aGraph, const RefPtr<Promise>& aPromise) 245 : ControlMessage(nullptr), mGraph(aGraph), mPromise(aPromise) { 246 MOZ_ASSERT(NS_IsMainThread()); 247 } 248 249 void Run() override { 250 TRACE("DOMMediaStream::Counter") 251 uint32_t streams = 252 mGraph->mTracks.Length() + mGraph->mSuspendedTracks.Length(); 253 mGraph->DispatchToMainThreadStableState(NS_NewRunnableFunction( 254 "DOMMediaStream::CountUnderlyingStreams (stable state)", 255 [promise = std::move(mPromise), streams]() mutable { 256 NS_DispatchToMainThread(NS_NewRunnableFunction( 257 "DOMMediaStream::CountUnderlyingStreams", 258 [promise = std::move(promise), streams]() { 259 promise->MaybeResolve(streams); 260 })); 261 })); 262 } 263 264 // mPromise can only be AddRefed/Released on main thread. 265 // In case of shutdown, Run() does not run, so we dispatch mPromise to be 266 // released on main thread here. 267 void RunDuringShutdown() override { 268 NS_ReleaseOnMainThread( 269 "DOMMediaStream::CountUnderlyingStreams::Counter::RunDuringShutdown", 270 mPromise.forget()); 271 } 272 273 private: 274 // mGraph owns this Counter instance and decides its lifetime. 275 MediaTrackGraphImpl* mGraph; 276 RefPtr<Promise> mPromise; 277 }; 278 graphImpl->AppendMessage(MakeUnique<Counter>(graphImpl, p)); 279 280 return p.forget(); 281 } 282 283 void DOMMediaStream::GetId(nsAString& aID) const { aID = mID; } 284 285 void DOMMediaStream::GetAudioTracks( 286 nsTArray<RefPtr<AudioStreamTrack>>& aTracks) const { 287 for (const auto& track : mTracks) { 288 if (AudioStreamTrack* t = track->AsAudioStreamTrack()) { 289 aTracks.AppendElement(t); 290 } 291 } 292 } 293 294 void DOMMediaStream::GetAudioTracks( 295 nsTArray<RefPtr<MediaStreamTrack>>& aTracks) const { 296 for (const auto& track : mTracks) { 297 if (track->AsAudioStreamTrack()) { 298 aTracks.AppendElement(track); 299 } 300 } 301 } 302 303 void DOMMediaStream::GetVideoTracks( 304 nsTArray<RefPtr<VideoStreamTrack>>& aTracks) const { 305 for (const auto& track : mTracks) { 306 if (VideoStreamTrack* t = track->AsVideoStreamTrack()) { 307 aTracks.AppendElement(t); 308 } 309 } 310 } 311 312 void DOMMediaStream::GetVideoTracks( 313 nsTArray<RefPtr<MediaStreamTrack>>& aTracks) const { 314 for (const auto& track : mTracks) { 315 if (track->AsVideoStreamTrack()) { 316 aTracks.AppendElement(track); 317 } 318 } 319 } 320 321 void DOMMediaStream::GetTracks( 322 nsTArray<RefPtr<MediaStreamTrack>>& aTracks) const { 323 for (const auto& track : mTracks) { 324 aTracks.AppendElement(track); 325 } 326 } 327 328 void DOMMediaStream::AddTrack(MediaStreamTrack& aTrack) { 329 LOG(LogLevel::Info, ("DOMMediaStream %p Adding track %p (from track %p)", 330 this, &aTrack, aTrack.GetTrack())); 331 332 if (HasTrack(aTrack)) { 333 LOG(LogLevel::Debug, 334 ("DOMMediaStream %p already contains track %p", this, &aTrack)); 335 return; 336 } 337 338 mTracks.AppendElement(&aTrack); 339 340 if (!aTrack.Ended()) { 341 NotifyTrackAdded(&aTrack); 342 } 343 } 344 345 void DOMMediaStream::RemoveTrack(MediaStreamTrack& aTrack) { 346 if (static_cast<LogModule*>(gMediaStreamLog)->ShouldLog(LogLevel::Info)) { 347 if (aTrack.Ended()) { 348 LOG(LogLevel::Info, 349 ("DOMMediaStream %p Removing (ended) track %p", this, &aTrack)); 350 } else { 351 LOG(LogLevel::Info, 352 ("DOMMediaStream %p Removing track %p (from track %p)", this, &aTrack, 353 aTrack.GetTrack())); 354 } 355 } 356 357 if (!mTracks.RemoveElement(&aTrack)) { 358 LOG(LogLevel::Debug, 359 ("DOMMediaStream %p does not contain track %p", this, &aTrack)); 360 return; 361 } 362 363 if (!aTrack.Ended()) { 364 NotifyTrackRemoved(&aTrack); 365 } 366 } 367 368 already_AddRefed<DOMMediaStream> DOMMediaStream::Clone() { 369 auto newStream = MakeRefPtr<DOMMediaStream>(GetOwnerWindow()); 370 371 LOG(LogLevel::Info, 372 ("DOMMediaStream %p created clone %p", this, newStream.get())); 373 374 for (const auto& track : mTracks) { 375 LOG(LogLevel::Debug, 376 ("DOMMediaStream %p forwarding external track %p to clone %p", this, 377 track.get(), newStream.get())); 378 RefPtr<MediaStreamTrack> clone = track->Clone(); 379 newStream->AddTrack(*clone); 380 } 381 382 return newStream.forget(); 383 } 384 385 bool DOMMediaStream::Active() const { return mActive; } 386 bool DOMMediaStream::Audible() const { return mAudible; } 387 388 MediaStreamTrack* DOMMediaStream::GetTrackById(const nsAString& aId) const { 389 for (const auto& track : mTracks) { 390 nsString id; 391 track->GetId(id); 392 if (id == aId) { 393 return track; 394 } 395 } 396 return nullptr; 397 } 398 399 bool DOMMediaStream::HasTrack(const MediaStreamTrack& aTrack) const { 400 return mTracks.Contains(&aTrack); 401 } 402 403 void DOMMediaStream::AddTrackInternal(MediaStreamTrack* aTrack) { 404 LOG(LogLevel::Debug, 405 ("DOMMediaStream %p Adding owned track %p", this, aTrack)); 406 AddTrack(*aTrack); 407 DispatchTrackEvent(u"addtrack"_ns, aTrack); 408 } 409 410 void DOMMediaStream::RemoveTrackInternal(MediaStreamTrack* aTrack) { 411 LOG(LogLevel::Debug, 412 ("DOMMediaStream %p Removing owned track %p", this, aTrack)); 413 if (!HasTrack(*aTrack)) { 414 return; 415 } 416 RemoveTrack(*aTrack); 417 DispatchTrackEvent(u"removetrack"_ns, aTrack); 418 } 419 420 already_AddRefed<nsIPrincipal> DOMMediaStream::GetPrincipal() { 421 nsGlobalWindowInner* win = GetOwnerWindow(); 422 if (!win) { 423 return nullptr; 424 } 425 nsCOMPtr<nsIPrincipal> principal = win->GetPrincipal(); 426 for (const auto& t : mTracks) { 427 if (t->Ended()) { 428 continue; 429 } 430 nsContentUtils::CombineResourcePrincipals(&principal, t->GetPrincipal()); 431 } 432 return principal.forget(); 433 } 434 435 void DOMMediaStream::NotifyActive() { 436 LOG(LogLevel::Info, ("DOMMediaStream %p NotifyActive(). ", this)); 437 438 MOZ_ASSERT(mActive); 439 for (int32_t i = mTrackListeners.Length() - 1; i >= 0; --i) { 440 mTrackListeners[i]->NotifyActive(); 441 } 442 } 443 444 void DOMMediaStream::NotifyInactive() { 445 LOG(LogLevel::Info, ("DOMMediaStream %p NotifyInactive(). ", this)); 446 447 MOZ_ASSERT(!mActive); 448 for (int32_t i = mTrackListeners.Length() - 1; i >= 0; --i) { 449 mTrackListeners[i]->NotifyInactive(); 450 } 451 } 452 453 void DOMMediaStream::NotifyAudible() { 454 LOG(LogLevel::Info, ("DOMMediaStream %p NotifyAudible(). ", this)); 455 456 MOZ_ASSERT(mAudible); 457 for (int32_t i = mTrackListeners.Length() - 1; i >= 0; --i) { 458 mTrackListeners[i]->NotifyAudible(); 459 } 460 } 461 462 void DOMMediaStream::NotifyInaudible() { 463 LOG(LogLevel::Info, ("DOMMediaStream %p NotifyInaudible(). ", this)); 464 465 MOZ_ASSERT(!mAudible); 466 for (int32_t i = mTrackListeners.Length() - 1; i >= 0; --i) { 467 mTrackListeners[i]->NotifyInaudible(); 468 } 469 } 470 471 void DOMMediaStream::RegisterTrackListener(TrackListener* aListener) { 472 MOZ_ASSERT(NS_IsMainThread()); 473 474 mTrackListeners.AppendElement(aListener); 475 } 476 477 void DOMMediaStream::UnregisterTrackListener(TrackListener* aListener) { 478 MOZ_ASSERT(NS_IsMainThread()); 479 mTrackListeners.RemoveElement(aListener); 480 } 481 482 void DOMMediaStream::NotifyTrackAdded(const RefPtr<MediaStreamTrack>& aTrack) { 483 MOZ_ASSERT(NS_IsMainThread()); 484 485 aTrack->AddConsumer(mPlaybackTrackListener); 486 487 for (int32_t i = mTrackListeners.Length() - 1; i >= 0; --i) { 488 mTrackListeners[i]->NotifyTrackAdded(aTrack); 489 } 490 491 if (!mActive) { 492 // Check if we became active. 493 if (ContainsLiveTracks(mTracks)) { 494 mActive = true; 495 NotifyActive(); 496 } 497 } 498 499 if (!mAudible) { 500 // Check if we became audible. 501 if (ContainsLiveAudioTracks(mTracks)) { 502 mAudible = true; 503 NotifyAudible(); 504 } 505 } 506 } 507 508 void DOMMediaStream::NotifyTrackRemoved( 509 const RefPtr<MediaStreamTrack>& aTrack) { 510 MOZ_ASSERT(NS_IsMainThread()); 511 512 if (aTrack) { 513 // aTrack may be null to allow HTMLMediaElement::MozCaptureStream streams 514 // to be played until the source media element has ended. The source media 515 // element will then call NotifyTrackRemoved(nullptr) to signal that we can 516 // go inactive, regardless of the timing of the last track ending. 517 518 aTrack->RemoveConsumer(mPlaybackTrackListener); 519 520 for (int32_t i = mTrackListeners.Length() - 1; i >= 0; --i) { 521 mTrackListeners[i]->NotifyTrackRemoved(aTrack); 522 } 523 524 if (!mActive) { 525 NS_ASSERTION(false, "Shouldn't remove a live track if already inactive"); 526 return; 527 } 528 } 529 530 if (mAudible) { 531 // Check if we became inaudible. 532 if (!ContainsLiveAudioTracks(mTracks)) { 533 mAudible = false; 534 NotifyInaudible(); 535 } 536 } 537 538 // Check if we became inactive. 539 if (!ContainsLiveTracks(mTracks)) { 540 mActive = false; 541 NotifyInactive(); 542 } 543 } 544 545 nsresult DOMMediaStream::DispatchTrackEvent( 546 const nsAString& aName, const RefPtr<MediaStreamTrack>& aTrack) { 547 MediaStreamTrackEventInit init; 548 init.mTrack = aTrack; 549 550 RefPtr<MediaStreamTrackEvent> event = 551 MediaStreamTrackEvent::Constructor(this, aName, init); 552 553 return DispatchTrustedEvent(event); 554 }