Example #1
0
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);
}
Example #2
0
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;
}
Example #3
0
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));
        }
    }
}
Example #4
0
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);
}
Example #6
0
  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;
  }
Example #7
0
/**
 * 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;
}