summaryrefslogtreecommitdiffstats
path: root/modules/webrtcUI.jsm
diff options
context:
space:
mode:
Diffstat (limited to 'modules/webrtcUI.jsm')
-rw-r--r--modules/webrtcUI.jsm292
1 files changed, 292 insertions, 0 deletions
diff --git a/modules/webrtcUI.jsm b/modules/webrtcUI.jsm
new file mode 100644
index 0000000..819ca18
--- /dev/null
+++ b/modules/webrtcUI.jsm
@@ -0,0 +1,292 @@
+/* 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/. */
+
+"use strict";
+
+this.EXPORTED_SYMBOLS = ["webrtcUI"];
+
+const Cu = Components.utils;
+const Cc = Components.classes;
+const Ci = Components.interfaces;
+
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(this, "PluralForm",
+ "resource://gre/modules/PluralForm.jsm");
+
+XPCOMUtils.defineLazyServiceGetter(this, "MediaManagerService",
+ "@mozilla.org/mediaManagerService;1",
+ "nsIMediaManagerService");
+
+this.webrtcUI = {
+ init: function () {
+ Services.obs.addObserver(handleRequest, "getUserMedia:request", false);
+ Services.obs.addObserver(updateIndicators, "recording-device-events", false);
+ Services.obs.addObserver(removeBrowserSpecificIndicator, "recording-window-ended", false);
+ },
+
+ uninit: function () {
+ Services.obs.removeObserver(handleRequest, "getUserMedia:request");
+ Services.obs.removeObserver(updateIndicators, "recording-device-events");
+ Services.obs.removeObserver(removeBrowserSpecificIndicator, "recording-window-ended");
+ },
+
+ showGlobalIndicator: false,
+
+ get activeStreams() {
+ let contentWindowSupportsArray = MediaManagerService.activeMediaCaptureWindows;
+ let count = contentWindowSupportsArray.Count();
+ let activeStreams = [];
+ for (let i = 0; i < count; i++) {
+ let contentWindow = contentWindowSupportsArray.GetElementAt(i);
+ let browser = getBrowserForWindow(contentWindow);
+ let browserWindow = browser.ownerDocument.defaultView;
+ let tab = browserWindow.gBrowser &&
+ browserWindow.gBrowser._getTabForContentWindow(contentWindow.top);
+ activeStreams.push({
+ uri: contentWindow.location.href,
+ tab: tab,
+ browser: browser
+ });
+ }
+ return activeStreams;
+ }
+}
+
+function getBrowserForWindowId(aWindowID) {
+ return getBrowserForWindow(Services.wm.getOuterWindowWithId(aWindowID));
+}
+
+function getBrowserForWindow(aContentWindow) {
+ return aContentWindow.QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIWebNavigation)
+ .QueryInterface(Ci.nsIDocShell)
+ .chromeEventHandler;
+}
+
+function handleRequest(aSubject, aTopic, aData) {
+ let {windowID: windowID, callID: callID} = JSON.parse(aData);
+
+ let params = aSubject.QueryInterface(Ci.nsIMediaStreamOptions);
+
+ Services.wm.getMostRecentWindow(null).navigator.mozGetUserMediaDevices(
+ function (devices) {
+ prompt(windowID, callID, params.audio, params.video || params.picture, devices);
+ },
+ function (error) {
+ // bug 827146 -- In the future, the UI should catch NO_DEVICES_FOUND
+ // and allow the user to plug in a device, instead of immediately failing.
+ denyRequest(callID, error);
+ }
+ );
+}
+
+function denyRequest(aCallID, aError) {
+ let msg = null;
+ if (aError) {
+ msg = Cc["@mozilla.org/supports-string;1"].createInstance(Ci.nsISupportsString);
+ msg.data = aError;
+ }
+ Services.obs.notifyObservers(msg, "getUserMedia:response:deny", aCallID);
+}
+
+function prompt(aWindowID, aCallID, aAudioRequested, aVideoRequested, aDevices) {
+ let audioDevices = [];
+ let videoDevices = [];
+ for (let device of aDevices) {
+ device = device.QueryInterface(Ci.nsIMediaDevice);
+ switch (device.type) {
+ case "audio":
+ if (aAudioRequested)
+ audioDevices.push(device);
+ break;
+ case "video":
+ if (aVideoRequested)
+ videoDevices.push(device);
+ break;
+ }
+ }
+
+ let requestType;
+ if (audioDevices.length && videoDevices.length)
+ requestType = "CameraAndMicrophone";
+ else if (audioDevices.length)
+ requestType = "Microphone";
+ else if (videoDevices.length)
+ requestType = "Camera";
+ else {
+ denyRequest(aCallID, "NO_DEVICES_FOUND");
+ return;
+ }
+
+ let contentWindow = Services.wm.getOuterWindowWithId(aWindowID);
+ let host = contentWindow.document.documentURIObject.host;
+ let browser = getBrowserForWindow(contentWindow);
+ let chromeDoc = browser.ownerDocument;
+ let chromeWin = chromeDoc.defaultView;
+ let stringBundle = chromeWin.gNavigatorBundle;
+ let message = stringBundle.getFormattedString("getUserMedia.share" + requestType + ".message",
+ [ host ]);
+
+ let mainAction = {
+ label: PluralForm.get(requestType == "CameraAndMicrophone" ? 2 : 1,
+ stringBundle.getString("getUserMedia.shareSelectedDevices.label")),
+ accessKey: stringBundle.getString("getUserMedia.shareSelectedDevices.accesskey"),
+ // The real callback will be set during the "showing" event. The
+ // empty function here is so that PopupNotifications.show doesn't
+ // reject the action.
+ callback: function() {}
+ };
+
+ let secondaryActions = [{
+ label: stringBundle.getString("getUserMedia.denyRequest.label"),
+ accessKey: stringBundle.getString("getUserMedia.denyRequest.accesskey"),
+ callback: function () {
+ denyRequest(aCallID);
+ }
+ }];
+
+ let options = {
+ eventCallback: function(aTopic, aNewBrowser) {
+ if (aTopic == "swapping")
+ return true;
+
+ if (aTopic != "showing")
+ return false;
+
+ let chromeDoc = this.browser.ownerDocument;
+
+ function listDevices(menupopup, devices) {
+ while (menupopup.lastChild)
+ menupopup.removeChild(menupopup.lastChild);
+
+ let deviceIndex = 0;
+ for (let device of devices) {
+ addDeviceToList(menupopup, device.name, deviceIndex);
+ deviceIndex++;
+ }
+ }
+
+ function addDeviceToList(menupopup, deviceName, deviceIndex) {
+ let menuitem = chromeDoc.createElement("menuitem");
+ menuitem.setAttribute("value", deviceIndex);
+ menuitem.setAttribute("label", deviceName);
+ menuitem.setAttribute("tooltiptext", deviceName);
+ menupopup.appendChild(menuitem);
+ }
+
+ chromeDoc.getElementById("webRTC-selectCamera").hidden = !videoDevices.length;
+ chromeDoc.getElementById("webRTC-selectMicrophone").hidden = !audioDevices.length;
+
+ let camMenupopup = chromeDoc.getElementById("webRTC-selectCamera-menupopup");
+ let micMenupopup = chromeDoc.getElementById("webRTC-selectMicrophone-menupopup");
+ listDevices(camMenupopup, videoDevices);
+ listDevices(micMenupopup, audioDevices);
+ if (requestType == "CameraAndMicrophone") {
+ let stringBundle = chromeDoc.defaultView.gNavigatorBundle;
+ addDeviceToList(camMenupopup, stringBundle.getString("getUserMedia.noVideo.label"), "-1");
+ addDeviceToList(micMenupopup, stringBundle.getString("getUserMedia.noAudio.label"), "-1");
+ }
+
+ this.mainAction.callback = function() {
+ let allowedDevices = Cc["@mozilla.org/supports-array;1"]
+ .createInstance(Ci.nsISupportsArray);
+ if (videoDevices.length) {
+ let videoDeviceIndex = chromeDoc.getElementById("webRTC-selectCamera-menulist").value;
+ if (videoDeviceIndex != "-1")
+ allowedDevices.AppendElement(videoDevices[videoDeviceIndex]);
+ }
+ if (audioDevices.length) {
+ let audioDeviceIndex = chromeDoc.getElementById("webRTC-selectMicrophone-menulist").value;
+ if (audioDeviceIndex != "-1")
+ allowedDevices.AppendElement(audioDevices[audioDeviceIndex]);
+ }
+
+ if (allowedDevices.Count() == 0) {
+ denyRequest(aCallID);
+ return;
+ }
+
+ Services.obs.notifyObservers(allowedDevices, "getUserMedia:response:allow", aCallID);
+ };
+ return true;
+ }
+ };
+
+ chromeWin.PopupNotifications.show(browser, "webRTC-shareDevices", message,
+ "webRTC-shareDevices-notification-icon", mainAction,
+ secondaryActions, options);
+}
+
+function updateIndicators() {
+ webrtcUI.showGlobalIndicator =
+ MediaManagerService.activeMediaCaptureWindows.Count() > 0;
+
+ let e = Services.wm.getEnumerator("navigator:browser");
+ while (e.hasMoreElements())
+ e.getNext().WebrtcIndicator.updateButton();
+
+ for (let {browser: browser} of webrtcUI.activeStreams)
+ showBrowserSpecificIndicator(browser);
+}
+
+function showBrowserSpecificIndicator(aBrowser) {
+ let hasVideo = {};
+ let hasAudio = {};
+ MediaManagerService.mediaCaptureWindowState(aBrowser.contentWindow,
+ hasVideo, hasAudio);
+ let captureState;
+ if (hasVideo.value && hasAudio.value) {
+ captureState = "CameraAndMicrophone";
+ } else if (hasVideo.value) {
+ captureState = "Camera";
+ } else if (hasAudio.value) {
+ captureState = "Microphone";
+ } else {
+ Cu.reportError("showBrowserSpecificIndicator: got neither video nor audio access");
+ return;
+ }
+
+ let chromeWin = aBrowser.ownerDocument.defaultView;
+ let stringBundle = chromeWin.gNavigatorBundle;
+
+ let message = stringBundle.getString("getUserMedia.sharing" + captureState + ".message2");
+
+ let windowId = aBrowser.contentWindow
+ .QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIDOMWindowUtils)
+ .currentInnerWindowID;
+ let mainAction = {
+ label: "Continue Sharing", //stringBundle.getString("getUserMedia.continueSharing.label"),
+ accessKey: "C", //stringBundle.getString("getUserMedia.continueSharing.accesskey"),
+ callback: function () {},
+ dismiss: true
+ };
+ let secondaryActions = [{
+ label: "Stop Sharing", //stringBundle.getString("getUserMedia.stopSharing.label"),
+ accessKey: "S", //stringBundle.getString("getUserMedia.stopSharing.accesskey"),
+ callback: function () {
+ Services.obs.notifyObservers(null, "getUserMedia:revoke", windowId);
+ }
+ }];
+ let options = {
+ hideNotNow: true,
+ dismissed: true,
+ eventCallback: function(aTopic) aTopic == "swapping"
+ };
+ chromeWin.PopupNotifications.show(aBrowser, "webRTC-sharingDevices", message,
+ "webRTC-sharingDevices-notification-icon", mainAction,
+ secondaryActions, options);
+}
+
+function removeBrowserSpecificIndicator(aSubject, aTopic, aData) {
+ let browser = getBrowserForWindowId(aData);
+ let PopupNotifications = browser.ownerDocument.defaultView.PopupNotifications;
+ let notification = PopupNotifications &&
+ PopupNotifications.getNotification("webRTC-sharingDevices",
+ browser);
+ if (notification)
+ PopupNotifications.remove(notification);
+}