bug 742832 - Add support for tab sharing for getUserMedia r=jesup
authorBrad Lassey <blassey@mozilla.com>
Thu, 17 Oct 2013 16:48:30 -0400
changeset 166054 c5391fab9cc1b28e98c88e789fc00b94dd817c32
parent 166053 171b129f5f933d1fa2a63a3ae90fc4747bcb80f4
child 166055 cc22bc5485669b4c50a5f0ba88f82ac7283ce2b2
push id428
push userbbajaj@mozilla.com
push dateTue, 28 Jan 2014 00:16:25 +0000
treeherdermozilla-release@cd72a7ff3a75 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjesup
bugs742832
milestone27.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
bug 742832 - Add support for tab sharing for getUserMedia r=jesup
content/media/webrtc/MediaEngineTabVideoSource.cpp
content/media/webrtc/MediaEngineTabVideoSource.h
content/media/webrtc/MediaEngineWebRTC.cpp
content/media/webrtc/MediaEngineWebRTC.h
content/media/webrtc/moz.build
content/media/webrtc/nsITabSource.idl
modules/libpref/src/init/all.js
new file mode 100644
--- /dev/null
+++ b/content/media/webrtc/MediaEngineTabVideoSource.cpp
@@ -0,0 +1,287 @@
+#include "nsGlobalWindow.h"
+#include "nsDOMWindowUtils.h"
+#include "nsIDOMClientRect.h"
+#include "nsIDocShell.h"
+#include "nsIPresShell.h"
+#include "nsPresContext.h"
+#include "gfxImageSurface.h"
+#include "gfxContext.h"
+#include "ImageContainer.h"
+#include "Layers.h"
+#include "nsIInterfaceRequestorUtils.h"
+#include "nsIDOMDocument.h"
+#include "nsITabSource.h"
+#include "MediaEngineTabVideoSource.h"
+#include "VideoUtils.h"
+#include "nsServiceManagerUtils.h"
+#include "nsIPrefService.h"
+namespace mozilla {
+
+NS_IMPL_ISUPPORTS1(MediaEngineTabVideoSource, MediaEngineVideoSource)
+
+MediaEngineTabVideoSource::MediaEngineTabVideoSource()
+  : mName(NS_LITERAL_STRING("share tab")), mUuid(NS_LITERAL_STRING("uuid")),
+  mMonitor("MediaEngineTabVideoSource")
+{
+}
+
+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);
+  }
+  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 = NULL;
+  }
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+MediaEngineTabVideoSource::HandleEvent(nsIDOMEvent *event) {
+  Draw();
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+MediaEngineTabVideoSource::Notify(nsITimer*) {
+  Draw();
+  return NS_OK;
+}
+
+nsresult
+MediaEngineTabVideoSource::InitRunnable::Run()
+{
+  nsresult rv;
+  nsCOMPtr<nsIPrefService> prefs = do_GetService("@mozilla.org/preferences-service;1", &rv);
+  NS_ENSURE_SUCCESS(rv, rv);
+  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);
+  NS_ENSURE_SUCCESS(rv, rv);
+  nsCOMPtr<nsIDOMWindow> win;
+  rv = tabSource->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;
+}
+
+void
+MediaEngineTabVideoSource::GetName(nsAString_internal& aName)
+{
+  aName.Assign(mName);
+
+}
+
+void
+MediaEngineTabVideoSource::GetUUID(nsAString_internal& aUuid)
+{
+  aUuid.Assign(mUuid);
+}
+
+nsresult
+MediaEngineTabVideoSource::Allocate(const mozilla::MediaEnginePrefs&)
+{
+  return NS_OK;
+}
+
+nsresult
+MediaEngineTabVideoSource::Deallocate()
+{
+  return NS_OK;
+}
+
+nsresult
+MediaEngineTabVideoSource::Start(mozilla::SourceMediaStream* aStream, mozilla::TrackID aID)
+{
+  nsCOMPtr<nsIRunnable> runnable;
+  if (!mWindow)
+    runnable = new InitRunnable(this);
+  else
+    runnable = new StartRunnable(this);
+  NS_DispatchToMainThread(runnable);
+  aStream->AddTrack(aID, USECS_PER_S, 0, new VideoSegment());
+  aStream->AdvanceKnownTracksTime(STREAM_TIME_MAX);
+
+  return NS_OK;
+}
+
+nsresult
+MediaEngineTabVideoSource::Snapshot(uint32_t, nsIDOMFile**)
+{
+  return NS_OK;
+}
+
+void
+MediaEngineTabVideoSource::
+NotifyPull(MediaStreamGraph*, SourceMediaStream* aSource, mozilla::TrackID aID, mozilla::StreamTime aDesiredTime, mozilla::TrackTicks& aLastEndTime)
+{
+  VideoSegment segment;
+  MonitorAutoLock mon(mMonitor);
+
+  // Note: we're not giving up mImage here
+  nsRefPtr<layers::CairoImage> image = mImage;
+  TrackTicks target = TimeToTicksRoundUp(USECS_PER_S, aDesiredTime);
+  TrackTicks delta = target - aLastEndTime;
+  if (delta > 0) {
+    // NULL images are allowed
+    if (image) {
+      gfxIntSize size = image->GetSize();
+      segment.AppendFrame(image.forget(), delta, size);
+    } else {
+      segment.AppendFrame(nullptr, delta, gfxIntSize(0,0));
+    }
+    // This can fail if either a) we haven't added the track yet, or b)
+    // we've removed or finished the track.
+    if (aSource->AppendToTrack(aID, &(segment))) {
+      aLastEndTime = target;
+    }
+  }
+}
+
+void
+MediaEngineTabVideoSource::Draw() {
+
+  nsIntSize size(mBufW, mBufH);
+
+  nsresult rv;
+  float scale = 1.0;
+
+  nsCOMPtr<nsPIDOMWindow> win = do_QueryInterface(mWindow);
+
+  if (!win) {
+    return;
+  }
+
+  // take a screenshot, as wide as possible, proportional to the destination size
+  nsCOMPtr<nsIDOMWindowUtils> utils = do_GetInterface(win);
+  if (!utils) {
+    return;
+  }
+
+  nsCOMPtr<nsIDOMClientRect> rect;
+  rv = utils->GetRootBounds(getter_AddRefs(rect));
+  NS_ENSURE_SUCCESS_VOID(rv);
+  if (!rect) {
+    return;
+  }
+
+  float left, top, width, height;
+  rect->GetLeft(&left);
+  rect->GetTop(&top);
+  rect->GetWidth(&width);
+  rect->GetHeight(&height);
+
+  if (width == 0 || height == 0) {
+    return;
+  }
+
+  int32_t srcX = left;
+  int32_t srcY = top;
+  int32_t srcW;
+  int32_t srcH;
+
+  float aspectRatio = ((float) size.width) / size.height;
+  if (width / aspectRatio < height) {
+    srcW = width;
+    srcH = width / aspectRatio;
+  } else {
+    srcW = height * aspectRatio;
+    srcH = height;
+  }
+
+  nsRefPtr<nsPresContext> presContext;
+  nsIDocShell* docshell = win->GetDocShell();
+  if (docshell) {
+    docshell->GetPresContext(getter_AddRefs(presContext));
+  }
+  if (!presContext) {
+    return;
+  }
+  nscolor bgColor = NS_RGB(255, 255, 255);
+  nsCOMPtr<nsIPresShell> presShell = presContext->PresShell();
+  uint32_t renderDocFlags = (nsIPresShell::RENDER_IGNORE_VIEWPORT_SCROLLING |
+                             nsIPresShell::RENDER_DOCUMENT_RELATIVE);
+  nsRect r(nsPresContext::CSSPixelsToAppUnits(srcX / scale),
+           nsPresContext::CSSPixelsToAppUnits(srcY / scale),
+           nsPresContext::CSSPixelsToAppUnits(srcW / scale),
+           nsPresContext::CSSPixelsToAppUnits(srcH / scale));
+
+  uint32_t stride = size.width * 4;
+
+  nsRefPtr<layers::ImageContainer> container = layers::LayerManager::CreateImageContainer();
+  nsRefPtr<gfxASurface> surf;
+  surf = new gfxImageSurface(static_cast<unsigned char*>(mData), size,
+                             stride, gfxImageFormatRGB24);
+  if (surf->CairoStatus() != 0) {
+    return;
+  }
+  nsRefPtr<gfxContext> context = new gfxContext(surf);
+  gfxPoint pt(0, 0);
+  context->Translate(pt);
+  context->Scale(scale * size.width / srcW, scale * size.height / srcH);
+  rv = presShell->RenderDocument(r, renderDocFlags, bgColor, context);
+
+  NS_ENSURE_SUCCESS_VOID(rv);
+
+  layers::CairoImage::Data cairoData;
+  cairoData.mSurface = surf;
+  cairoData.mSize = size;
+
+  ImageFormat cairoFormat = CAIRO_SURFACE;
+  nsRefPtr<layers::CairoImage> image = new layers::CairoImage();
+
+  image->SetData(cairoData);
+
+  MonitorAutoLock mon(mMonitor);
+  mImage = image;
+}
+
+nsresult
+MediaEngineTabVideoSource::Stop(mozilla::SourceMediaStream*, mozilla::TrackID)
+{
+  NS_DispatchToMainThread(new StopRunnable(this));
+  return NS_OK;
+}
+
+nsresult
+MediaEngineTabVideoSource::Config(bool, uint32_t, bool, uint32_t, bool, uint32_t)
+{
+  return NS_OK;
+}
+
+bool
+MediaEngineTabVideoSource::IsFake()
+{
+  return false;
+}
+
+}
new file mode 100644
--- /dev/null
+++ b/content/media/webrtc/MediaEngineTabVideoSource.h
@@ -0,0 +1,65 @@
+/* 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/. */
+
+#include "nsIDOMEventListener.h"
+#include "MediaEngine.h"
+#include "ImageContainer.h"
+#include "nsITimer.h"
+#include "mozilla/Monitor.h"
+
+namespace mozilla {
+
+class MediaEngineTabVideoSource : public MediaEngineVideoSource, nsIDOMEventListener, nsITimerCallback {
+  public:
+    NS_DECL_THREADSAFE_ISUPPORTS
+    NS_DECL_NSIDOMEVENTLISTENER
+    NS_DECL_NSITIMERCALLBACK
+    MediaEngineTabVideoSource();
+    ~MediaEngineTabVideoSource() { free(mData); }
+    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);
+    virtual nsresult Config(bool, uint32_t, bool, uint32_t, bool, uint32_t);
+    virtual bool IsFake();
+    void Draw();
+
+    class StartRunnable : public nsRunnable {
+    public:
+      StartRunnable(MediaEngineTabVideoSource *videoSource) : mVideoSource(videoSource) {}
+      NS_IMETHOD Run();
+      nsRefPtr<MediaEngineTabVideoSource> mVideoSource;
+    };
+
+    class StopRunnable : public nsRunnable {
+    public:
+    StopRunnable(MediaEngineTabVideoSource *videoSource) : mVideoSource(videoSource) {}
+      NS_IMETHOD Run();
+      nsRefPtr<MediaEngineTabVideoSource> mVideoSource;
+    };
+
+    class InitRunnable : public nsRunnable {
+    public:
+    InitRunnable(MediaEngineTabVideoSource *videoSource) : mVideoSource(videoSource) {}
+      NS_IMETHOD Run();
+      nsRefPtr<MediaEngineTabVideoSource> mVideoSource;
+    };
+
+private:
+    int mBufW;
+    int mBufH;
+    int mTimePerFrame;
+    unsigned char *mData;
+    nsCOMPtr<nsIDOMWindow> mWindow;
+    nsRefPtr<layers::CairoImage> mImage;
+    nsCOMPtr<nsITimer> mTimer;
+    nsAutoString mName, mUuid;
+    Monitor mMonitor;
+  };
+
+}
--- a/content/media/webrtc/MediaEngineWebRTC.cpp
+++ b/content/media/webrtc/MediaEngineWebRTC.cpp
@@ -24,24 +24,45 @@ GetUserMediaLog()
   if (!sLog)
     sLog = PR_NewLogModule("GetUserMedia");
   return sLog;
 }
 #endif
 
 #include "MediaEngineWebRTC.h"
 #include "ImageContainer.h"
+#include "nsIComponentRegistrar.h"
+#include "MediaEngineTabVideoSource.h"
+#include "nsITabSource.h"
+
 #ifdef MOZ_WIDGET_ANDROID
 #include "AndroidBridge.h"
 #endif
 
 #undef LOG
 #define LOG(args) PR_LOG(GetUserMediaLog(), PR_LOG_DEBUG, args)
 
 namespace mozilla {
+#ifndef MOZ_B2G_CAMERA
+MediaEngineWebRTC::MediaEngineWebRTC()
+  : mMutex("mozilla::MediaEngineWebRTC")
+  , mVideoEngine(nullptr)
+  , mVoiceEngine(nullptr)
+  , mVideoEngineInit(false)
+  , mAudioEngineInit(false)
+  , mHasTabVideoSource(false)
+{
+  nsCOMPtr<nsIComponentRegistrar> compMgr;
+  NS_GetComponentRegistrar(getter_AddRefs(compMgr));
+  if (compMgr) {
+    compMgr->IsContractIDRegistered(NS_TABSOURCESERVICE_CONTRACTID, &mHasTabVideoSource);
+  }
+}
+#endif
+
 
 void
 MediaEngineWebRTC::EnumerateVideoDevices(nsTArray<nsRefPtr<MediaEngineVideoSource> >* aVSources)
 {
 #ifdef MOZ_B2G_CAMERA
   MutexAutoLock lock(mMutex);
   if (!mCameraManager) {
     return;
@@ -95,16 +116,19 @@ MediaEngineWebRTC::EnumerateVideoDevices
   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) {
--- a/content/media/webrtc/MediaEngineWebRTC.h
+++ b/content/media/webrtc/MediaEngineWebRTC.h
@@ -348,28 +348,22 @@ public:
   MediaEngineWebRTC(nsDOMCameraManager* aCameraManager, uint64_t aWindowId)
     : mMutex("mozilla::MediaEngineWebRTC")
     , mVideoEngine(nullptr)
     , mVoiceEngine(nullptr)
     , mVideoEngineInit(false)
     , mAudioEngineInit(false)
     , mCameraManager(aCameraManager)
     , mWindowId(aWindowId)
+    , mHasTabVideoSource(false)
   {
     AsyncLatencyLogger::Get(true)->AddRef();
   }
 #else
-  MediaEngineWebRTC()
-    : mMutex("mozilla::MediaEngineWebRTC")
-    , mVideoEngine(nullptr)
-    , mVoiceEngine(nullptr)
-    , mVideoEngineInit(false)
-    , mAudioEngineInit(false)
-  {
-  }
+  MediaEngineWebRTC();
 #endif
   ~MediaEngineWebRTC() {
     Shutdown();
 #ifdef MOZ_B2G_CAMERA
     AsyncLatencyLogger::Get()->Release();
 #endif
   }
 
@@ -385,16 +379,17 @@ private:
   // protected with mMutex:
 
   webrtc::VideoEngine* mVideoEngine;
   webrtc::VoiceEngine* mVoiceEngine;
 
   // Need this to avoid unneccesary WebRTC calls while enumerating.
   bool mVideoEngineInit;
   bool mAudioEngineInit;
+  bool mHasTabVideoSource;
 
   // Store devices we've already seen in a hashtable for quick return.
   // Maps UUID to MediaEngineSource (one set for audio, one for video).
   nsRefPtrHashtable<nsStringHashKey, MediaEngineWebRTCVideoSource > mVideoSources;
   nsRefPtrHashtable<nsStringHashKey, MediaEngineWebRTCAudioSource > mAudioSources;
 
 #ifdef MOZ_B2G_CAMERA
   // MediaEngine hold this DOM object, and the MediaEngine is hold by Navigator
--- a/content/media/webrtc/moz.build
+++ b/content/media/webrtc/moz.build
@@ -1,28 +1,34 @@
 # -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*-
 # vim: set filetype=python:
 # 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/.
 
+XPIDL_MODULE = 'content_webrtc'
+
 MODULE = 'content'
 
 EXPORTS += [
     'MediaEngine.h',
     'MediaEngineDefault.h',
 ]
 
 if CONFIG['MOZ_WEBRTC']:
     EXPORTS += ['MediaEngineWebRTC.h']
     CPP_SOURCES += [
+        'MediaEngineTabVideoSource.cpp',
         'MediaEngineWebRTC.cpp',
         'MediaEngineWebRTCVideo.cpp',
         'MediaEngineWebRTCAudio.cpp',
     ]
+XPIDL_SOURCES += [
+        'nsITabSource.idl'
+]
 
 CPP_SOURCES += [
     'MediaEngineDefault.cpp',
 ]
 
 LIBXUL_LIBRARY = True
 
 LIBRARY_NAME = 'gkconwebrtc_s'
new file mode 100644
--- /dev/null
+++ b/content/media/webrtc/nsITabSource.idl
@@ -0,0 +1,17 @@
+/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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/. */
+
+#include "nsISupports.idl"
+#include "nsIDOMWindow.idl"
+
+[scriptable,uuid(ff9c0e45-4646-45ec-b2f0-3b16d9e41875)]
+interface nsITabSource : nsISupports
+{
+  nsIDOMWindow getTabToStream();
+};
+
+%{C++
+#define NS_TABSOURCESERVICE_CONTRACTID "@mozilla.org/tab-source-service;1"
+%}
--- a/modules/libpref/src/init/all.js
+++ b/modules/libpref/src/init/all.js
@@ -247,16 +247,21 @@ pref("media.peerconnection.agc_enabled",
 pref("media.peerconnection.agc", 1);
 pref("media.peerconnection.noise_enabled", false);
 pref("media.peerconnection.noise", 1);
 #else
 #ifdef ANDROID
 pref("media.navigator.enabled", true);
 #endif
 #endif
+
+pref("media.tabstreaming.width", 320);
+pref("media.tabstreaming.height", 240);
+pref("media.tabstreaming.time_per_frame", 40);
+
 // TextTrack support
 pref("media.webvtt.enabled", false);
 
 // Whether to enable MediaSource support
 pref("media.mediasource.enabled", false);
 
 #ifdef MOZ_WEBSPEECH
 pref("media.webspeech.recognition.enable", false);