Bug 1132882 - Slave the local video size to the remote video width on the Loop standalone UI. r=mikedeboer
authorMark Banner <standard8@mozilla.com>
Sat, 14 Feb 2015 09:48:11 +0000
changeset 256158 0c5caa6e7207fbb05cee40d8250ad096b6f89d58
parent 256157 09a43afd57b2e6a0ec6f9c170ebc341f117109e5
child 256159 7f9d625a5734983f370570b620b58c512a3cae39
push id4610
push userjlund@mozilla.com
push dateMon, 30 Mar 2015 18:32:55 +0000
treeherdermozilla-beta@4df54044d9ef [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmikedeboer
bugs1132882
milestone38.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 1132882 - Slave the local video size to the remote video width on the Loop standalone UI. r=mikedeboer
browser/components/loop/standalone/content/js/standaloneRoomViews.js
browser/components/loop/standalone/content/js/standaloneRoomViews.jsx
browser/components/loop/test/standalone/standaloneRoomViews_test.js
--- a/browser/components/loop/standalone/content/js/standaloneRoomViews.js
+++ b/browser/components/loop/standalone/content/js/standaloneRoomViews.js
@@ -290,40 +290,64 @@ loop.standaloneRoomViews = (function(moz
      * Specifically updates the local camera stream size and position, depending
      * on the size and position of the remote video stream.
      * This method gets called from `updateVideoContainer`, which is defined in
      * the `MediaSetupMixin`.
      *
      * @param  {Object} ratio Aspect ratio of the local camera stream
      */
     updateLocalCameraPosition: function(ratio) {
+      // The local stream is a quarter of the remote stream.
+      var LOCAL_STREAM_SIZE = 0.25;
+      // The local stream overlaps the remote stream by a quarter of the local stream.
+      var LOCAL_STREAM_OVERLAP = 0.25;
+      // The minimum size of video height/width allowed by the sdk css.
+      var SDK_MIN_SIZE = 48;
+
       var node = this._getElement(".local");
-      var parent = node.offsetParent || this._getElement(".media");
-      // The local camera view should be a sixth of the size of its offset parent
-      // and positioned to overlap with the remote stream at a quarter of its width.
-      var parentWidth = parent.offsetWidth;
-      var targetWidth = parentWidth / 6;
+      var targetWidth;
 
       node.style.right = "auto";
       if (window.matchMedia && window.matchMedia("screen and (max-width:640px)").matches) {
+        // For reduced screen widths, we just go for a fixed size and no overlap.
         targetWidth = 180;
+        node.style.width = (targetWidth * ratio.width) + "px";
+        node.style.height = (targetWidth * ratio.height) + "px";
         node.style.left = "auto";
       } else {
+        // The local camera view should be a quarter of the size of the remote stream
+        // and positioned to overlap with the remote stream at a quarter of its width.
+
         // Now position the local camera view correctly with respect to the remote
         // video stream.
         var remoteVideoDimensions = this.getRemoteVideoDimensions();
+        targetWidth = remoteVideoDimensions.streamWidth * LOCAL_STREAM_SIZE;
+
+        var realWidth = targetWidth * ratio.width;
+        var realHeight = targetWidth * ratio.height;
+
+        // If we've hit the min size limits, then limit at the minimum.
+        if (realWidth < SDK_MIN_SIZE) {
+          realWidth = SDK_MIN_SIZE;
+          realHeight = realWidth / ratio.width * ratio.height;
+        }
+        if (realHeight < SDK_MIN_SIZE) {
+          realHeight = SDK_MIN_SIZE;
+          realWidth = realHeight / ratio.height * ratio.width;
+        }
+
         var offsetX = (remoteVideoDimensions.streamWidth + remoteVideoDimensions.offsetX);
         // The horizontal offset of the stream, and the width of the resulting
         // pillarbox, is determined by the height exponent of the aspect ratio.
         // Therefore we multiply the width of the local camera view by the height
         // ratio.
-        node.style.left = (offsetX - ((targetWidth * ratio.height) / 4)) + "px";
+        node.style.left = (offsetX - (realWidth * LOCAL_STREAM_OVERLAP)) + "px";
+        node.style.width = realWidth + "px";
+        node.style.height = realHeight + "px";
       }
-      node.style.width = (targetWidth * ratio.width) + "px";
-      node.style.height = (targetWidth * ratio.height) + "px";
     },
 
     /**
      * Checks if current room is active.
      *
      * @return {Boolean}
      */
     _roomIsActive: function() {
--- a/browser/components/loop/standalone/content/js/standaloneRoomViews.jsx
+++ b/browser/components/loop/standalone/content/js/standaloneRoomViews.jsx
@@ -290,40 +290,64 @@ loop.standaloneRoomViews = (function(moz
      * Specifically updates the local camera stream size and position, depending
      * on the size and position of the remote video stream.
      * This method gets called from `updateVideoContainer`, which is defined in
      * the `MediaSetupMixin`.
      *
      * @param  {Object} ratio Aspect ratio of the local camera stream
      */
     updateLocalCameraPosition: function(ratio) {
+      // The local stream is a quarter of the remote stream.
+      var LOCAL_STREAM_SIZE = 0.25;
+      // The local stream overlaps the remote stream by a quarter of the local stream.
+      var LOCAL_STREAM_OVERLAP = 0.25;
+      // The minimum size of video height/width allowed by the sdk css.
+      var SDK_MIN_SIZE = 48;
+
       var node = this._getElement(".local");
-      var parent = node.offsetParent || this._getElement(".media");
-      // The local camera view should be a sixth of the size of its offset parent
-      // and positioned to overlap with the remote stream at a quarter of its width.
-      var parentWidth = parent.offsetWidth;
-      var targetWidth = parentWidth / 6;
+      var targetWidth;
 
       node.style.right = "auto";
       if (window.matchMedia && window.matchMedia("screen and (max-width:640px)").matches) {
+        // For reduced screen widths, we just go for a fixed size and no overlap.
         targetWidth = 180;
+        node.style.width = (targetWidth * ratio.width) + "px";
+        node.style.height = (targetWidth * ratio.height) + "px";
         node.style.left = "auto";
       } else {
+        // The local camera view should be a quarter of the size of the remote stream
+        // and positioned to overlap with the remote stream at a quarter of its width.
+
         // Now position the local camera view correctly with respect to the remote
         // video stream.
         var remoteVideoDimensions = this.getRemoteVideoDimensions();
+        targetWidth = remoteVideoDimensions.streamWidth * LOCAL_STREAM_SIZE;
+
+        var realWidth = targetWidth * ratio.width;
+        var realHeight = targetWidth * ratio.height;
+
+        // If we've hit the min size limits, then limit at the minimum.
+        if (realWidth < SDK_MIN_SIZE) {
+          realWidth = SDK_MIN_SIZE;
+          realHeight = realWidth / ratio.width * ratio.height;
+        }
+        if (realHeight < SDK_MIN_SIZE) {
+          realHeight = SDK_MIN_SIZE;
+          realWidth = realHeight / ratio.height * ratio.width;
+        }
+
         var offsetX = (remoteVideoDimensions.streamWidth + remoteVideoDimensions.offsetX);
         // The horizontal offset of the stream, and the width of the resulting
         // pillarbox, is determined by the height exponent of the aspect ratio.
         // Therefore we multiply the width of the local camera view by the height
         // ratio.
-        node.style.left = (offsetX - ((targetWidth * ratio.height) / 4)) + "px";
+        node.style.left = (offsetX - (realWidth * LOCAL_STREAM_OVERLAP)) + "px";
+        node.style.width = realWidth + "px";
+        node.style.height = realHeight + "px";
       }
-      node.style.width = (targetWidth * ratio.width) + "px";
-      node.style.height = (targetWidth * ratio.height) + "px";
     },
 
     /**
      * Checks if current room is active.
      *
      * @return {Boolean}
      */
     _roomIsActive: function() {
--- a/browser/components/loop/test/standalone/standaloneRoomViews_test.js
+++ b/browser/components/loop/test/standalone/standaloneRoomViews_test.js
@@ -138,16 +138,118 @@ describe("loop.standaloneRoomViews", fun
         sinon.assert.calledOnce(dispatch);
         sinon.assert.calledWithExactly(dispatch, new sharedActions.SetMute({
           type: "video",
           enabled: true
         }));
       });
     });
 
+    describe("Local Stream Size Position", function() {
+      var view, localElement;
+
+      beforeEach(function() {
+        sandbox.stub(window, "matchMedia").returns({
+          matches: false
+        });
+        view = mountTestComponent();
+        localElement = view._getElement(".local");
+      });
+
+      it("should be a quarter of the width of the main stream", function() {
+        sandbox.stub(view, "getRemoteVideoDimensions").returns({
+          streamWidth: 640,
+          offsetX: 0
+        });
+
+        view.updateLocalCameraPosition({
+          width: 1,
+          height: 0.75
+        });
+
+        expect(localElement.style.width).eql("160px");
+        expect(localElement.style.height).eql("120px");
+      });
+
+      it("should be a quarter of the width reduced for aspect ratio", function() {
+        sandbox.stub(view, "getRemoteVideoDimensions").returns({
+          streamWidth: 640,
+          offsetX: 0
+        });
+
+        view.updateLocalCameraPosition({
+          width: 0.75,
+          height: 1
+        });
+
+        expect(localElement.style.width).eql("120px");
+        expect(localElement.style.height).eql("160px");
+      });
+
+      it("should ensure the height is a minimum of 48px", function() {
+        sandbox.stub(view, "getRemoteVideoDimensions").returns({
+          streamWidth: 180,
+          offsetX: 0
+        });
+
+        view.updateLocalCameraPosition({
+          width: 1,
+          height: 0.75
+        });
+
+        expect(localElement.style.width).eql("64px");
+        expect(localElement.style.height).eql("48px");
+      });
+
+      it("should ensure the width is a minimum of 48px", function() {
+        sandbox.stub(view, "getRemoteVideoDimensions").returns({
+          streamWidth: 180,
+          offsetX: 0
+        });
+
+        view.updateLocalCameraPosition({
+          width: 0.75,
+          height: 1
+        });
+
+        expect(localElement.style.width).eql("48px");
+        expect(localElement.style.height).eql("64px");
+      });
+
+      it("should position the stream to overlap the main stream by a quarter", function() {
+        sandbox.stub(view, "getRemoteVideoDimensions").returns({
+          streamWidth: 640,
+          offsetX: 0
+        });
+
+        view.updateLocalCameraPosition({
+          width: 1,
+          height: 0.75
+        });
+
+        expect(localElement.style.width).eql("160px");
+        expect(localElement.style.left).eql("600px");
+      });
+
+      it("should position the stream to overlap the main stream by a quarter when the aspect ratio is vertical", function() {
+        sandbox.stub(view, "getRemoteVideoDimensions").returns({
+          streamWidth: 640,
+          offsetX: 0
+        });
+
+        view.updateLocalCameraPosition({
+          width: 0.75,
+          height: 1
+        });
+
+        expect(localElement.style.width).eql("120px");
+        expect(localElement.style.left).eql("610px");
+      });
+    });
+
     describe("#render", function() {
       var view;
 
       beforeEach(function() {
         view = mountTestComponent();
       });
 
       describe("Empty room message", function() {