bug 928096 - UI for Tab streaming r=mfinkle
authorBrad Lassey <blassey@mozilla.com>
Tue, 18 Mar 2014 15:05:46 -0400
changeset 174175 af15bd900a2a7a6a28b8cc97903abadb05b9b20a
parent 174174 0b7e95999fffb01bd343833cd9f025857251424f
child 174176 cf2d9b80ab283e31e56bb40be30a935db0195c1b
push idunknown
push userunknown
push dateunknown
reviewersmfinkle
bugs928096
milestone31.0a1
bug 928096 - UI for Tab streaming r=mfinkle
content/media/webrtc/MediaEngineTabVideoSource.cpp
content/media/webrtc/MediaEngineTabVideoSource.h
content/media/webrtc/MediaEngineWebRTC.cpp
content/media/webrtc/nsITabSource.idl
mobile/android/chrome/content/WebrtcUI.js
mobile/android/components/MobileComponents.manifest
mobile/android/components/TabSource.js
mobile/android/components/moz.build
mobile/android/installer/package-manifest.in
mobile/android/locales/en-US/chrome/browser.properties
--- a/content/media/webrtc/MediaEngineTabVideoSource.cpp
+++ b/content/media/webrtc/MediaEngineTabVideoSource.cpp
@@ -18,47 +18,57 @@
 #include "nsIPrefService.h"
 namespace mozilla {
 
 using namespace mozilla::gfx;
 
 NS_IMPL_ISUPPORTS1(MediaEngineTabVideoSource, MediaEngineVideoSource)
 
 MediaEngineTabVideoSource::MediaEngineTabVideoSource()
-  : mName(NS_LITERAL_STRING("share tab")), mUuid(NS_LITERAL_STRING("uuid")),
+: mName(NS_LITERAL_STRING("&getUserMedia.videoDevice.tabShare;")),
+  mUuid(NS_LITERAL_STRING("uuid")),
+  mData(0),
   mMonitor("MediaEngineTabVideoSource")
 {
 }
 
+MediaEngineTabVideoSource::~MediaEngineTabVideoSource()
+{
+  if (mData)
+      free(mData);
+}
+
 nsresult
 MediaEngineTabVideoSource::StartRunnable::Run()
 {
   mVideoSource->Draw();
   nsCOMPtr<nsPIDOMWindow> privateDOMWindow = do_QueryInterface(mVideoSource->mWindow);
   if (privateDOMWindow) {
     privateDOMWindow->GetChromeEventHandler()->AddEventListener(NS_LITERAL_STRING("MozAfterPaint"), mVideoSource, false);
   } else {
     mVideoSource->mTimer = do_CreateInstance(NS_TIMER_CONTRACTID);
     mVideoSource->mTimer->InitWithCallback(mVideoSource, mVideoSource->mTimePerFrame, nsITimer:: TYPE_REPEATING_SLACK);
   }
+  mVideoSource->mTabSource->NotifyStreamStart(mVideoSource->mWindow);
   return NS_OK;
 }
 
 nsresult
 MediaEngineTabVideoSource::StopRunnable::Run()
 {
   nsCOMPtr<nsPIDOMWindow> privateDOMWindow = do_QueryInterface(mVideoSource->mWindow);
   if (privateDOMWindow && mVideoSource && privateDOMWindow->GetChromeEventHandler()) {
     privateDOMWindow->GetChromeEventHandler()->RemoveEventListener(NS_LITERAL_STRING("MozAfterPaint"), mVideoSource, false);
   }
 
   if (mVideoSource->mTimer) {
     mVideoSource->mTimer->Cancel();
     mVideoSource->mTimer = nullptr;
   }
+  mVideoSource->mTabSource->NotifyStreamStop(mVideoSource->mWindow);
   return NS_OK;
 }
 
 NS_IMETHODIMP
 MediaEngineTabVideoSource::HandleEvent(nsIDOMEvent *event) {
   Draw();
   return NS_OK;
 }
@@ -78,20 +88,21 @@ MediaEngineTabVideoSource::InitRunnable:
   nsCOMPtr<nsIPrefBranch> branch = do_QueryInterface(prefs);
   if (!branch)
     return NS_OK;
   branch->GetIntPref("media.tabstreaming.width", &mVideoSource->mBufW);
   branch->GetIntPref("media.tabstreaming.height", &mVideoSource->mBufH);
   branch->GetIntPref("media.tabstreaming.time_per_frame", &mVideoSource->mTimePerFrame);
   mVideoSource->mData = (unsigned char*)malloc(mVideoSource->mBufW * mVideoSource->mBufH * 4);
 
-  nsCOMPtr<nsITabSource> tabSource = do_GetService(NS_TABSOURCESERVICE_CONTRACTID, &rv);
+  mVideoSource->mTabSource = do_GetService(NS_TABSOURCESERVICE_CONTRACTID, &rv);
   NS_ENSURE_SUCCESS(rv, rv);
+
   nsCOMPtr<nsIDOMWindow> win;
-  rv = tabSource->GetTabToStream(getter_AddRefs(win));
+  rv = mVideoSource->mTabSource->GetTabToStream(getter_AddRefs(win));
   NS_ENSURE_SUCCESS(rv, rv);
   if (!win)
     return NS_OK;
 
   mVideoSource->mWindow = win;
   nsCOMPtr<nsIRunnable> start(new StartRunnable(mVideoSource));
   start->Run();
   return NS_OK;
--- a/content/media/webrtc/MediaEngineTabVideoSource.h
+++ b/content/media/webrtc/MediaEngineTabVideoSource.h
@@ -2,26 +2,28 @@
  * 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/. */
 
 #include "nsIDOMEventListener.h"
 #include "MediaEngine.h"
 #include "ImageContainer.h"
 #include "nsITimer.h"
 #include "mozilla/Monitor.h"
+#include "nsITabSource.h"
 
 namespace mozilla {
 
 class MediaEngineTabVideoSource : public MediaEngineVideoSource, nsIDOMEventListener, nsITimerCallback {
   public:
     NS_DECL_THREADSAFE_ISUPPORTS
     NS_DECL_NSIDOMEVENTLISTENER
     NS_DECL_NSITIMERCALLBACK
     MediaEngineTabVideoSource();
-    ~MediaEngineTabVideoSource() { free(mData); }
+    ~MediaEngineTabVideoSource();
+
     virtual void GetName(nsAString_internal&);
     virtual void GetUUID(nsAString_internal&);
     virtual nsresult Allocate(const mozilla::MediaEnginePrefs&);
     virtual nsresult Deallocate();
     virtual nsresult Start(mozilla::SourceMediaStream*, mozilla::TrackID);
     virtual nsresult Snapshot(uint32_t, nsIDOMFile**);
     virtual void NotifyPull(mozilla::MediaStreamGraph*, mozilla::SourceMediaStream*, mozilla::TrackID, mozilla::StreamTime, mozilla::TrackTicks&);
     virtual nsresult Stop(mozilla::SourceMediaStream*, mozilla::TrackID);
@@ -55,11 +57,11 @@ private:
     int mBufH;
     int mTimePerFrame;
     unsigned char *mData;
     nsCOMPtr<nsIDOMWindow> mWindow;
     nsRefPtr<layers::CairoImage> mImage;
     nsCOMPtr<nsITimer> mTimer;
     nsAutoString mName, mUuid;
     Monitor mMonitor;
+    nsCOMPtr<nsITabSource> mTabSource;
   };
-
 }
--- a/content/media/webrtc/MediaEngineWebRTC.cpp
+++ b/content/media/webrtc/MediaEngineWebRTC.cpp
@@ -116,20 +116,16 @@ MediaEngineWebRTC::EnumerateVideoDevices
   // get the JVM
   JavaVM *jvm = mozilla::AndroidBridge::Bridge()->GetVM();
 
   if (webrtc::VideoEngine::SetAndroidObjects(jvm, (void*)context) != 0) {
     LOG(("VieCapture:SetAndroidObjects Failed"));
     return;
   }
 #endif
-
-  if (mHasTabVideoSource)
-    aVSources->AppendElement(new MediaEngineTabVideoSource());
-
   if (!mVideoEngine) {
     if (!(mVideoEngine = webrtc::VideoEngine::Create())) {
       return;
     }
   }
 
   PRLogModuleInfo *logs = GetWebRTCLogInfo();
   if (!gWebrtcTraceLoggingOn && logs && logs->level > 0) {
@@ -224,16 +220,19 @@ MediaEngineWebRTC::EnumerateVideoDevices
       aVSources->AppendElement(vSource.get());
     } else {
       vSource = new MediaEngineWebRTCVideoSource(mVideoEngine, i);
       mVideoSources.Put(uuid, vSource); // Hashtable takes ownership.
       aVSources->AppendElement(vSource);
     }
   }
 
+  if (mHasTabVideoSource)
+    aVSources->AppendElement(new MediaEngineTabVideoSource());
+
   return;
 #endif
 }
 
 void
 MediaEngineWebRTC::EnumerateAudioDevices(nsTArray<nsRefPtr<MediaEngineAudioSource> >* aASources)
 {
   ScopedCustomReleasePtr<webrtc::VoEBase> ptrVoEBase;
--- a/content/media/webrtc/nsITabSource.idl
+++ b/content/media/webrtc/nsITabSource.idl
@@ -5,13 +5,15 @@
 
 #include "nsISupports.idl"
 #include "nsIDOMWindow.idl"
 
 [scriptable,uuid(ff9c0e45-4646-45ec-b2f0-3b16d9e41875)]
 interface nsITabSource : nsISupports
 {
   nsIDOMWindow getTabToStream();
+  void notifyStreamStart(in nsIDOMWindow window);
+  void notifyStreamStop(in nsIDOMWindow window);
 };
 
 %{C++
 #define NS_TABSOURCESERVICE_CONTRACTID "@mozilla.org/tab-source-service;1"
 %}
--- a/mobile/android/chrome/content/WebrtcUI.js
+++ b/mobile/android/chrome/content/WebrtcUI.js
@@ -119,17 +119,20 @@ var WebrtcUI = {
 
   // Get a list of string names for devices. Ensures that none of the strings are blank
   _getList: function(aDevices, aType) {
     let defaultCount = 0;
     return aDevices.map(function(device) {
         // if this is a Camera input, convert the name to something readable
         let res = /Camera\ \d+,\ Facing (front|back)/.exec(device.name);
         if (res)
-          return Strings.browser.GetStringFromName("getUserMedia." + aType + "." + res[1]);
+          return Strings.browser.GetStringFromName("getUserMedia." + aType + "." + res[1] + "Camera");
+
+        if (device.name.startsWith("&") && device.name.endsWith(";"))
+          return Strings.browser.GetStringFromName(device.name.substring(1, device.name.length -1));
 
         if (device.name.trim() == "") {
           defaultCount++;
           return Strings.browser.formatStringFromName("getUserMedia." + aType + ".default", [defaultCount], 1);
         }
         return device.name
       }, this);
   },
--- a/mobile/android/components/MobileComponents.manifest
+++ b/mobile/android/components/MobileComponents.manifest
@@ -95,16 +95,20 @@ contract @mozilla.org/dom/site-specific-
 # PaymentsUI.js
 component {3c6c9575-f57e-427b-a8aa-57bc3cbff48f} PaymentsUI.js
 contract @mozilla.org/payment/ui-glue;1 {3c6c9575-f57e-427b-a8aa-57bc3cbff48f}
 
 # FilePicker.js
 component {18a4e042-7c7c-424b-a583-354e68553a7f} FilePicker.js
 contract @mozilla.org/filepicker;1 {18a4e042-7c7c-424b-a583-354e68553a7f}
 
+# TabSource.js
+component {5850c76e-b916-4218-b99a-31f004e0a7e7} TabSource.js
+contract @mozilla.org/tab-source-service;1 {5850c76e-b916-4218-b99a-31f004e0a7e7}
+
 # Snippets.js
 component {a78d7e59-b558-4321-a3d6-dffe2f1e76dd} Snippets.js
 contract @mozilla.org/snippets;1 {a78d7e59-b558-4321-a3d6-dffe2f1e76dd}
 category profile-after-change Snippets @mozilla.org/snippets;1
 category update-timer Snippets @mozilla.org/snippets;1,getService,snippets-update-timer,browser.snippets.updateInterval,86400
 
 # WebappsUpdateTimer.js
 component {8f7002cb-e959-4f0a-a2e8-563232564385} WebappsUpdateTimer.js
new file mode 100644
--- /dev/null
+++ b/mobile/android/components/TabSource.js
@@ -0,0 +1,75 @@
+const { classes: Cc, interfaces: Ci, manager: Cm, utils: Cu, results: Cr } = Components;
+
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource://gre/modules/Prompt.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(this, "sendMessageToJava",
+                                  "resource://gre/modules/Messaging.jsm");
+
+function TabSource() {
+}
+
+TabSource.prototype = {
+  classID: Components.ID("{5850c76e-b916-4218-b99a-31f004e0a7e7}"),
+  classDescription: "Fennec Tab Source",
+  contractID: "@mozilla.org/tab-source-service;1",
+  QueryInterface: XPCOMUtils.generateQI([Ci.nsITabSource]),
+
+  getTabToStream: function() {
+    let app = Services.wm.getMostRecentWindow("navigator:browser").BrowserApp;
+    let tabs = app.tabs;
+    if (tabs == null || tabs.length == 0) {
+      Services.console.logStringMessage("ERROR: No tabs");
+      return null;
+    }
+
+    let bundle = Services.strings.createBundle("chrome://browser/locale/browser.properties");
+    let title = bundle.GetStringFromName("tabshare.title")
+
+    let prompt = new Prompt({
+      title: title,
+      window: null
+    }).setSingleChoiceItems(tabs.map(function(tab) {
+      return { label: tab.browser.contentTitle || tab.browser.contentURI.spec }
+    }));
+
+    let result = null;
+    prompt.show(function(data) {
+      result = data.button;
+    });
+
+    // Spin this thread while we wait for a result.
+    let thread = Services.tm.currentThread;
+    while (result == null) {
+      thread.processNextEvent(true);
+    }
+
+    if (result == -1) {
+      return null;
+    }
+    return tabs[result].browser.contentWindow;
+  },
+
+  notifyStreamStart: function(window) {
+    let app = Services.wm.getMostRecentWindow("navigator:browser").BrowserApp;
+    let tabs = app.tabs;
+    for (var i in tabs) {
+      if (tabs[i].browser.contentWindow == window) {
+        sendMessageToJava({ type: "Tab:Streaming", tabID: tabs[i].id });
+      }
+    }
+  },
+
+  notifyStreamStop: function(window) {
+    let app = Services.wm.getMostRecentWindow("navigator:browser").BrowserApp;
+    let tabs = app.tabs;
+    for (let i in tabs) {
+      if (tabs[i].browser.contentWindow == window) {
+        sendMessageToJava({ type: "Tab:NotStreaming", tabID: tabs[i].id });
+      }
+    }
+  }
+};
+
+this.NSGetFactory = XPCOMUtils.generateNSGetFactory([TabSource]);
--- a/mobile/android/components/moz.build
+++ b/mobile/android/components/moz.build
@@ -19,16 +19,17 @@ EXTRA_COMPONENTS += [
     'DownloadManagerUI.js',
     'FilePicker.js',
     'LoginManagerPrompter.js',
     'NSSDialogService.js',
     'PaymentsUI.js',
     'PromptService.js',
     'SiteSpecificUserAgent.js',
     'Snippets.js',
+    'TabSource.js',
     'WebappsUpdateTimer.js',
     'XPIDialogService.js',
 ]
 
 EXTRA_PP_COMPONENTS += [
     'AboutRedirector.js',
     'BrowserCLH.js',
     'DirectoryProvider.js',
--- a/mobile/android/installer/package-manifest.in
+++ b/mobile/android/installer/package-manifest.in
@@ -330,16 +330,17 @@
 @BINPATH@/components/NetworkGeolocationProvider.js
 @BINPATH@/components/nsSidebar.manifest
 @BINPATH@/components/nsSidebar.js
 @BINPATH@/components/extensions.manifest
 @BINPATH@/components/addonManager.js
 @BINPATH@/components/amContentHandler.js
 @BINPATH@/components/amWebInstallListener.js
 @BINPATH@/components/nsBlocklistService.js
+@BINPATH@/components/TabSource.js
 @BINPATH@/components/webvtt.xpt
 @BINPATH@/components/WebVTT.manifest
 @BINPATH@/components/WebVTTParserWrapper.js
 
 #ifdef MOZ_UPDATER
 @BINPATH@/components/nsUpdateService.manifest
 @BINPATH@/components/nsUpdateService.js
 @BINPATH@/components/nsUpdateServiceStub.js
--- a/mobile/android/locales/en-US/chrome/browser.properties
+++ b/mobile/android/locales/en-US/chrome/browser.properties
@@ -297,35 +297,38 @@ lwthemeInstallRequest.allowButton=Allow
 
 # LOCALIZATION NOTE (getUserMedia.shareCamera.message, getUserMedia.shareMicrophone.message, getUserMedia.shareCameraAndMicrophone.message, getUserMedia.sharingCamera.message, getUserMedia.sharingMicrophone.message, getUserMedia.sharingCameraAndMicrophone.message): %S is the website origin (e.g. www.mozilla.org)
 getUserMedia.shareCamera.message = Would you like to share your camera with %S?
 getUserMedia.shareMicrophone.message = Would you like to share your microphone with %S?
 getUserMedia.shareCameraAndMicrophone.message = Would you like to share your camera and microphone with %S?
 getUserMedia.denyRequest.label = Don't Share
 getUserMedia.shareRequest.label = Share
 getUserMedia.videoDevice.default = Camera %S
-getUserMedia.videoDevice.front = Front facing
-getUserMedia.videoDevice.back = Back facing
+getUserMedia.videoDevice.frontCamera = Front facing camera
+getUserMedia.videoDevice.backCamera = Back facing camera
 getUserMedia.videoDevice.none = No Video
-getUserMedia.videoDevice.prompt = Camera to use
+getUserMedia.videoDevice.tabShare = Choose a tab to stream
+getUserMedia.videoDevice.prompt = Video source
 getUserMedia.audioDevice.default = Microphone %S
 getUserMedia.audioDevice.none = No Audio
 getUserMedia.audioDevice.prompt = Microphone to use
 getUserMedia.sharingCamera.message2 = Camera is on
 getUserMedia.sharingMicrophone.message2 = Microphone is on
 getUserMedia.sharingCameraAndMicrophone.message2 = Camera and microphone are on
 
 #Reader mode
 readerMode.enter = Enter Reader Mode
 readerMode.exit = Exit Reader Mode
 
 #Open in App
 openInApp.pageAction = Open in App
 openInApp.ok = OK
 openInApp.cancel = Cancel
 
+#Tab sharing
+tabshare.title = "Choose a tab to stream"
 #Tabs in context menus
 browser.menu.context.default = Link
 browser.menu.context.img = Image
 browser.menu.context.video = Video
 browser.menu.context.audio = Audio
 browser.menu.context.tel = Phone
 browser.menu.context.mailto = Mail