void MediaManager::OnNavigation(uint64_t aWindowID) { NS_ASSERTION(NS_IsMainThread(), "OnNavigation called off main thread"); // Invalidate this window. The runnables check this value before making // a call to content. // This is safe since we're on main-thread, and the windowlist can only // be added to from the main-thread StreamListeners* listeners = GetWindowListeners(aWindowID); if (!listeners) { return; } uint32_t length = listeners->Length(); for (uint32_t i = 0; i < length; i++) { nsRefPtr<GetUserMediaCallbackMediaStreamListener> listener = listeners->ElementAt(i); listener->Invalidate(); listener->Remove(); } listeners->Clear(); GetActiveWindows()->Remove(aWindowID); }
nsresult MediaManager::MediaCaptureWindowStateInternal(nsIDOMWindow* aWindow, bool* aVideo, bool* aAudio) { // We need to return the union of all streams in all innerwindows that // correspond to that outerwindow. // Iterate the docshell tree to find all the child windows, find // all the listeners for each one, get the booleans, and merge the // results. nsCOMPtr<nsPIDOMWindow> piWin = do_QueryInterface(aWindow); if (piWin) { if (piWin->GetCurrentInnerWindow() || piWin->IsInnerWindow()) { uint64_t windowID; if (piWin->GetCurrentInnerWindow()) { windowID = piWin->GetCurrentInnerWindow()->WindowID(); } else { windowID = piWin->WindowID(); } StreamListeners* listeners = GetActiveWindows()->Get(windowID); if (listeners) { uint32_t length = listeners->Length(); for (uint32_t i = 0; i < length; ++i) { nsRefPtr<GetUserMediaCallbackMediaStreamListener> listener = listeners->ElementAt(i); if (listener->CapturingVideo()) { *aVideo = true; } if (listener->CapturingAudio()) { *aAudio = true; } if (*aAudio && *aVideo) { return NS_OK; // no need to continue iterating } } } } // iterate any children of *this* window (iframes, etc) nsCOMPtr<nsIDocShellTreeNode> node = do_QueryInterface(piWin->GetDocShell()); if (node) { int32_t i, count; node->GetChildCount(&count); for (i = 0; i < count; ++i) { nsCOMPtr<nsIDocShellTreeItem> item; node->GetChildAt(i, getter_AddRefs(item)); nsCOMPtr<nsPIDOMWindow> win = do_GetInterface(item); MediaCaptureWindowStateInternal(win, aVideo, aAudio); if (*aAudio && *aVideo) { return NS_OK; // no need to continue iterating } } } } return NS_OK; }
void MediaManager::RemoveFromWindowList(uint64_t aWindowID, GetUserMediaCallbackMediaStreamListener *aListener) { NS_ASSERTION(NS_IsMainThread(), "RemoveFromWindowList called off main thread"); // This is defined as safe on an inactive GUMCMSListener aListener->Remove(); // really queues the remove StreamListeners* listeners = GetWindowListeners(aWindowID); if (!listeners) { return; } listeners->RemoveElement(aListener); if (listeners->Length() == 0) { RemoveWindowID(aWindowID); // listeners has been deleted here // get outer windowID nsPIDOMWindow *window = static_cast<nsPIDOMWindow*> (nsGlobalWindow::GetInnerWindowWithId(aWindowID)); if (window) { nsPIDOMWindow *outer = window->GetOuterWindow(); if (outer) { uint64_t outerID = outer->WindowID(); // Notify the UI that this window no longer has gUM active char windowBuffer[32]; PR_snprintf(windowBuffer, sizeof(windowBuffer), "%llu", outerID); nsAutoString data; data.Append(NS_ConvertUTF8toUTF16(windowBuffer)); nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService(); obs->NotifyObservers(nullptr, "recording-window-ended", data.get()); LOG(("Sent recording-window-ended for window %llu (outer %llu)", aWindowID, outerID)); } else { LOG(("No outer window for inner %llu", aWindowID)); } } else { LOG(("No inner window for %llu", aWindowID)); } } }
void MediaManager::RemoveFromWindowList(uint64_t aWindowID, GetUserMediaCallbackMediaStreamListener *aListener) { NS_ASSERTION(NS_IsMainThread(), "RemoveFromWindowList called off main thread"); // This is defined as safe on an inactive GUMCMSListener aListener->Remove(); // really queues the remove StreamListeners* listeners = GetWindowListeners(aWindowID); if (!listeners) { return; } listeners->RemoveElement(aListener); if (listeners->Length() == 0) { RemoveWindowID(aWindowID); // listeners has been deleted here } }
void MediaManager::OnNavigation(PRUint64 aWindowID) { // Invalidate this window. The runnables check this value before making // a call to content. StreamListeners* listeners = mActiveWindows.Get(aWindowID); if (!listeners) { return; } PRUint32 length = listeners->Length(); for (PRUint32 i = 0; i < length; i++) { nsRefPtr<GetUserMediaCallbackMediaStreamListener> listener = listeners->ElementAt(i); listener->Invalidate(); listener = nsnull; } listeners->Clear(); mActiveWindows.Remove(aWindowID); }
NS_IMETHOD Run() { NS_ASSERTION(NS_IsMainThread(), "Only call on main thread"); // We're on main-thread, and the windowlist can only // be invalidated from the main-thread (see OnNavigation) StreamListeners* listeners = mManager->GetWindowListeners(mWindowID); if (!listeners) { // This window is no longer live. return NS_OK; } // Create a media stream. uint32_t hints = (mAudioSource ? nsDOMMediaStream::HINT_CONTENTS_AUDIO : 0); hints |= (mVideoSource ? nsDOMMediaStream::HINT_CONTENTS_VIDEO : 0); nsRefPtr<nsDOMUserMediaStream> trackunion = nsDOMUserMediaStream::CreateTrackUnionStream(hints); if (!trackunion) { nsCOMPtr<nsIDOMGetUserMediaErrorCallback> error(mError); LOG(("Returning error for getUserMedia() - no stream")); error->OnError(NS_LITERAL_STRING("NO_STREAM")); return NS_OK; } MediaStreamGraph* gm = MediaStreamGraph::GetInstance(); nsRefPtr<SourceMediaStream> stream = gm->CreateSourceStream(nullptr); // connect the source stream to the track union stream to avoid us blocking trackunion->GetStream()->AsProcessedStream()->SetAutofinish(true); nsRefPtr<MediaInputPort> port = trackunion->GetStream()->AsProcessedStream()-> AllocateInputPort(stream, MediaInputPort::FLAG_BLOCK_OUTPUT); trackunion->mSourceStream = stream; trackunion->mPort = port; nsPIDOMWindow *window = static_cast<nsPIDOMWindow*> (nsGlobalWindow::GetInnerWindowWithId(mWindowID)); if (window && window->GetExtantDoc()) { trackunion->CombineWithPrincipal(window->GetExtantDoc()->NodePrincipal()); } // Ensure there's a thread for gum to proxy to off main thread nsIThread *mediaThread = MediaManager::GetThread(); // Add our listener. We'll call Start() on the source when get a callback // that the MediaStream has started consuming. The listener is freed // when the page is invalidated (on navigation or close). GetUserMediaCallbackMediaStreamListener* listener = new GetUserMediaCallbackMediaStreamListener(mediaThread, stream.forget(), port.forget(), mAudioSource, mVideoSource); listener->Stream()->AddListener(listener); // No need for locking because we always do this in the main thread. listeners->AppendElement(listener); // Dispatch to the media thread to ask it to start the sources, // because that can take a while nsRefPtr<MediaOperationRunnable> runnable( new MediaOperationRunnable(MEDIA_START, listener, mAudioSource, mVideoSource)); mediaThread->Dispatch(runnable, NS_DISPATCH_NORMAL); // We're in the main thread, so no worries here either. nsCOMPtr<nsIDOMGetUserMediaSuccessCallback> success(mSuccess); nsCOMPtr<nsIDOMGetUserMediaErrorCallback> error(mError); if (!(mManager->IsWindowStillActive(mWindowID))) { return NS_OK; } // This is safe since we're on main-thread, and the windowlist can only // be invalidated from the main-thread (see OnNavigation) LOG(("Returning success for getUserMedia()")); success->OnSuccess(static_cast<nsIDOMLocalMediaStream*>(trackunion)); return NS_OK; }
/** * The entry point for this file. A call from Navigator::mozGetUserMedia * will end up here. MediaManager is a singleton that is responsible * for handling all incoming getUserMedia calls from every window. */ nsresult MediaManager::GetUserMedia(bool aPrivileged, nsPIDOMWindow* aWindow, nsIMediaStreamOptions* aParams, nsIDOMGetUserMediaSuccessCallback* aOnSuccess, nsIDOMGetUserMediaErrorCallback* aOnError) { NS_ASSERTION(NS_IsMainThread(), "Only call on main thread"); NS_ENSURE_TRUE(aParams, NS_ERROR_NULL_POINTER); NS_ENSURE_TRUE(aWindow, NS_ERROR_NULL_POINTER); NS_ENSURE_TRUE(aOnError, NS_ERROR_NULL_POINTER); NS_ENSURE_TRUE(aOnSuccess, NS_ERROR_NULL_POINTER); nsCOMPtr<nsIDOMGetUserMediaSuccessCallback> onSuccess(aOnSuccess); nsCOMPtr<nsIDOMGetUserMediaErrorCallback> onError(aOnError); /* Get options */ nsresult rv; bool fake, audio, video, picture; rv = aParams->GetFake(&fake); NS_ENSURE_SUCCESS(rv, rv); rv = aParams->GetPicture(&picture); NS_ENSURE_SUCCESS(rv, rv); rv = aParams->GetAudio(&audio); NS_ENSURE_SUCCESS(rv, rv); rv = aParams->GetVideo(&video); NS_ENSURE_SUCCESS(rv, rv); nsCOMPtr<nsIMediaDevice> audiodevice; rv = aParams->GetAudioDevice(getter_AddRefs(audiodevice)); NS_ENSURE_SUCCESS(rv, rv); nsCOMPtr<nsIMediaDevice> videodevice; rv = aParams->GetVideoDevice(getter_AddRefs(videodevice)); NS_ENSURE_SUCCESS(rv, rv); // If a device was provided, make sure it support the type of stream requested. if (audiodevice) { nsString type; audiodevice->GetType(type); if (audio && !type.EqualsLiteral("audio")) { return NS_ERROR_FAILURE; } } if (videodevice) { nsString type; videodevice->GetType(type); if ((picture || video) && !type.EqualsLiteral("video")) { return NS_ERROR_FAILURE; } } // We only support "front" or "back". TBD: Send to GetUserMediaRunnable. nsString cameraType; rv = aParams->GetCamera(cameraType); NS_ENSURE_SUCCESS(rv, rv); /** * If we were asked to get a picture, before getting a snapshot, we check if * the calling page is allowed to open a popup. We do this because * {picture:true} will open a new "window" to let the user preview or select * an image, on Android. The desktop UI for {picture:true} is TBD, at which * may point we can decide whether to extend this test there as well. */ #if !defined(MOZ_WEBRTC) if (picture && !aPrivileged) { if (aWindow->GetPopupControlState() > openControlled) { nsCOMPtr<nsIPopupWindowManager> pm = do_GetService(NS_POPUPWINDOWMANAGER_CONTRACTID); if (!pm) { return NS_OK; } uint32_t permission; nsCOMPtr<nsIDocument> doc = aWindow->GetExtantDoc(); pm->TestPermission(doc->NodePrincipal(), &permission); if (permission == nsIPopupWindowManager::DENY_POPUP) { nsCOMPtr<nsIDOMDocument> domDoc = aWindow->GetExtantDocument(); nsGlobalWindow::FirePopupBlockedEvent( domDoc, aWindow, nullptr, EmptyString(), EmptyString() ); return NS_OK; } } } #endif static bool created = false; if (!created) { // Force MediaManager to startup before we try to access it from other threads // Hack: should init singleton earlier unless it's expensive (mem or CPU) (void) MediaManager::Get(); } // Store the WindowID in a hash table and mark as active. The entry is removed // when this window is closed or navigated away from. uint64_t windowID = aWindow->WindowID(); nsRefPtr<GetUserMediaRunnable> gUMRunnable; // This is safe since we're on main-thread, and the windowlist can only // be invalidated from the main-thread (see OnNavigation) StreamListeners* listeners = GetActiveWindows()->Get(windowID); if (!listeners) { listeners = new StreamListeners; GetActiveWindows()->Put(windowID, listeners); } // Ensure there's a thread for gum to proxy to off main thread nsIThread *mediaThread = MediaManager::GetThread(); // Create a disabled listener to act as a placeholder GetUserMediaCallbackMediaStreamListener* listener = new GetUserMediaCallbackMediaStreamListener(mediaThread, windowID); // No need for locking because we always do this in the main thread. listeners->AppendElement(listener); // Developer preference for turning off permission check. if (Preferences::GetBool("media.navigator.permission.disabled", false)) { aPrivileged = true; } /** * Pass runnables along to GetUserMediaRunnable so it can add the * MediaStreamListener to the runnable list. The last argument can * optionally be a MediaDevice object, which should provided if one was * selected by the user via the UI, or was provided by privileged code * via the device: attribute via nsIMediaStreamOptions. * * If a fake stream was requested, we force the use of the default backend. */ if (fake) { // Fake stream from default backend. gUMRunnable = new GetUserMediaRunnable( audio, video, onSuccess.forget(), onError.forget(), windowID, listener, new MediaEngineDefault() ); } else if (audiodevice || videodevice) { // Stream from provided device. gUMRunnable = new GetUserMediaRunnable( audio, video, picture, onSuccess.forget(), onError.forget(), windowID, listener, static_cast<MediaDevice*>(audiodevice.get()), static_cast<MediaDevice*>(videodevice.get()) ); } else { // Stream from default device from WebRTC backend. gUMRunnable = new GetUserMediaRunnable( audio, video, picture, onSuccess.forget(), onError.forget(), windowID, listener ); } #ifdef ANDROID if (picture) { // ShowFilePickerForMimeType() must run on the Main Thread! (on Android) NS_DispatchToMainThread(gUMRunnable); return NS_OK; } #endif // XXX No full support for picture in Desktop yet (needs proper UI) if (aPrivileged || fake) { mMediaThread->Dispatch(gUMRunnable, NS_DISPATCH_NORMAL); } else { // Ask for user permission, and dispatch runnable (or not) when a response // is received via an observer notification. Each call is paired with its // runnable by a GUID. nsresult rv; nsCOMPtr<nsIUUIDGenerator> uuidgen = do_GetService("@mozilla.org/uuid-generator;1", &rv); NS_ENSURE_SUCCESS(rv, rv); // Generate a call ID. nsID id; rv = uuidgen->GenerateUUIDInPlace(&id); NS_ENSURE_SUCCESS(rv, rv); char buffer[NSID_LENGTH]; id.ToProvidedString(buffer); NS_ConvertUTF8toUTF16 callID(buffer); // Store the current callback. mActiveCallbacks.Put(callID, gUMRunnable); // Construct JSON structure with both the windowID and the callID. nsAutoString data; data.Append(NS_LITERAL_STRING("{\"windowID\":")); // Convert window ID to string. char windowBuffer[32]; PR_snprintf(windowBuffer, sizeof(windowBuffer), "%llu", aWindow->GetOuterWindow()->WindowID()); data.Append(NS_ConvertUTF8toUTF16(windowBuffer)); data.Append(NS_LITERAL_STRING(", \"callID\":\"")); data.Append(callID); data.Append(NS_LITERAL_STRING("\"}")); nsCOMPtr<nsIObserverService> obs = services::GetObserverService(); obs->NotifyObservers(aParams, "getUserMedia:request", data.get()); } return NS_OK; }