Bug 1088650 Add sounds for notifications when rooms are joined, left or if there are failures. r=nperriault a=lsblakk
authorMark Banner <standard8@mozilla.com>
Tue, 18 Nov 2014 20:50:57 +0000
changeset 233976 0b191751b9402f1fb54b0009dab59456d53ef542
parent 233975 2a62197daafe2b1b019ba6ec4def60072a5197a9
child 233977 9614d1f5e9671790d38e108e3133badfe1b9a69f
push id1
push usersledru@mozilla.com
push dateThu, 04 Dec 2014 17:57:20 +0000
reviewersnperriault, lsblakk
bugs1088650
milestone35.0a2
Bug 1088650 Add sounds for notifications when rooms are joined, left or if there are failures. r=nperriault a=lsblakk
browser/components/loop/content/js/roomViews.js
browser/components/loop/content/js/roomViews.jsx
browser/components/loop/content/shared/js/mixins.js
browser/components/loop/content/shared/sounds/room-joined-in.ogg
browser/components/loop/content/shared/sounds/room-left.ogg
browser/components/loop/standalone/content/js/standaloneRoomViews.js
browser/components/loop/standalone/content/js/standaloneRoomViews.jsx
browser/components/loop/test/shared/mixins_test.js
--- a/browser/components/loop/content/js/roomViews.js
+++ b/browser/components/loop/content/js/roomViews.js
@@ -7,16 +7,17 @@
 /* jshint newcap:false */
 /* global loop:true, React */
 
 var loop = loop || {};
 loop.roomViews = (function(mozL10n) {
   "use strict";
 
   var sharedActions = loop.shared.actions;
+  var sharedMixins = loop.shared.mixins;
   var ROOM_STATES = loop.store.ROOM_STATES;
   var sharedViews = loop.shared.views;
 
   function noop() {}
 
   /**
    * ActiveRoomStore mixin.
    * @type {Object}
@@ -127,17 +128,21 @@ loop.roomViews = (function(mozL10n) {
       );
     }
   });
 
   /**
    * Desktop room conversation view.
    */
   var DesktopRoomConversationView = React.createClass({displayName: 'DesktopRoomConversationView',
-    mixins: [ActiveRoomStoreMixin, loop.shared.mixins.DocumentTitleMixin],
+    mixins: [
+      ActiveRoomStoreMixin,
+      sharedMixins.DocumentTitleMixin,
+      sharedMixins.RoomsAudioMixin
+    ],
 
     propTypes: {
       dispatcher: React.PropTypes.instanceOf(loop.Dispatcher).isRequired
     },
 
     _renderInvitationOverlay: function() {
       if (this.state.roomState !== ROOM_STATES.HAS_PARTICIPANTS) {
         return DesktopRoomInvitationView({
--- a/browser/components/loop/content/js/roomViews.jsx
+++ b/browser/components/loop/content/js/roomViews.jsx
@@ -7,16 +7,17 @@
 /* jshint newcap:false */
 /* global loop:true, React */
 
 var loop = loop || {};
 loop.roomViews = (function(mozL10n) {
   "use strict";
 
   var sharedActions = loop.shared.actions;
+  var sharedMixins = loop.shared.mixins;
   var ROOM_STATES = loop.store.ROOM_STATES;
   var sharedViews = loop.shared.views;
 
   function noop() {}
 
   /**
    * ActiveRoomStore mixin.
    * @type {Object}
@@ -127,17 +128,21 @@ loop.roomViews = (function(mozL10n) {
       );
     }
   });
 
   /**
    * Desktop room conversation view.
    */
   var DesktopRoomConversationView = React.createClass({
-    mixins: [ActiveRoomStoreMixin, loop.shared.mixins.DocumentTitleMixin],
+    mixins: [
+      ActiveRoomStoreMixin,
+      sharedMixins.DocumentTitleMixin,
+      sharedMixins.RoomsAudioMixin
+    ],
 
     propTypes: {
       dispatcher: React.PropTypes.instanceOf(loop.Dispatcher).isRequired
     },
 
     _renderInvitationOverlay: function() {
       if (this.state.roomState !== ROOM_STATES.HAS_PARTICIPANTS) {
         return <DesktopRoomInvitationView
--- a/browser/components/loop/content/shared/js/mixins.js
+++ b/browser/components/loop/content/shared/js/mixins.js
@@ -221,18 +221,75 @@ loop.shared.mixins = (function() {
     /**
      * Ensures audio is stopped when the component is unmounted.
      */
     componentWillUnmount: function() {
       this._ensureAudioStopped();
     }
   };
 
+  /**
+   * A mixin especially for rooms. This plays the right sound according to
+   * the state changes. Requires AudioMixin to also be used.
+   */
+  var RoomsAudioMixin = {
+    mixins: [AudioMixin],
+
+    componentWillUpdate: function(nextProps, nextState) {
+      var ROOM_STATES = loop.store.ROOM_STATES;
+
+      function isConnectedToRoom(state) {
+        return state === ROOM_STATES.HAS_PARTICIPANTS ||
+          state === ROOM_STATES.SESSION_CONNECTED;
+      }
+
+      function notConnectedToRoom(state) {
+        // Failed and full are states that the user is not
+        // really connected o the room, but we don't want to
+        // catch those here, as they get their own sounds.
+        return state === ROOM_STATES.INIT ||
+          state === ROOM_STATES.GATHER ||
+          state === ROOM_STATES.READY ||
+          state === ROOM_STATES.JOINED;
+      }
+
+      // Joining the room.
+      if (notConnectedToRoom(this.state.roomState) &&
+          isConnectedToRoom(nextState.roomState)) {
+        this.play("room-joined");
+      }
+
+      // Other people coming and leaving.
+      if (this.state.roomState === ROOM_STATES.SESSION_CONNECTED &&
+          nextState.roomState === ROOM_STATES.HAS_PARTICIPANTS) {
+        this.play("room-joined-in");
+      }
+
+      if (this.state.roomState === ROOM_STATES.HAS_PARTICIPANTS &&
+          nextState.roomState === ROOM_STATES.SESSION_CONNECTED) {
+        this.play("room-left");
+      }
+
+      // Leaving the room - same sound as if a participant leaves
+      if (isConnectedToRoom(this.state.roomState) &&
+          notConnectedToRoom(nextState.roomState)) {
+        this.play("room-left");
+      }
+
+      // Room failures
+      if (nextState.roomState === ROOM_STATES.FAILED ||
+          nextState.roomState === ROOM_STATES.FULL) {
+        this.play("failure");
+      }
+    }
+  };
+
   return {
     AudioMixin: AudioMixin,
+    RoomsAudioMixin: RoomsAudioMixin,
     setRootObject: setRootObject,
     DropdownMenuMixin: DropdownMenuMixin,
     DocumentVisibilityMixin: DocumentVisibilityMixin,
     DocumentLocationMixin: DocumentLocationMixin,
     DocumentTitleMixin: DocumentTitleMixin,
     UrlHashChangeMixin: UrlHashChangeMixin
   };
 })();
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..164325c63d6072b4de3ca0f2a5cfb476dabf3a2e
GIT binary patch
literal 9242
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
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..d70fb260839d2eaf7b250b31f3c7e01b25ed8431
GIT binary patch
literal 10028
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
--- a/browser/components/loop/standalone/content/js/standaloneRoomViews.js
+++ b/browser/components/loop/standalone/content/js/standaloneRoomViews.js
@@ -136,17 +136,20 @@ loop.standaloneRoomViews = (function(moz
           React.DOM.p({dangerouslySetInnerHTML: {__html: this._getContent()}}), 
           React.DOM.div({className: "footer-logo"})
         )
       );
     }
   });
 
   var StandaloneRoomView = React.createClass({displayName: 'StandaloneRoomView',
-    mixins: [Backbone.Events],
+    mixins: [
+      Backbone.Events,
+      sharedMixins.RoomsAudioMixin
+    ],
 
     propTypes: {
       activeRoomStore:
         React.PropTypes.instanceOf(loop.store.ActiveRoomStore).isRequired,
       dispatcher: React.PropTypes.instanceOf(loop.Dispatcher).isRequired,
       helper: React.PropTypes.instanceOf(loop.shared.utils.Helper).isRequired
     },
 
--- a/browser/components/loop/standalone/content/js/standaloneRoomViews.jsx
+++ b/browser/components/loop/standalone/content/js/standaloneRoomViews.jsx
@@ -136,17 +136,20 @@ loop.standaloneRoomViews = (function(moz
           <p dangerouslySetInnerHTML={{__html: this._getContent()}}></p>
           <div className="footer-logo" />
         </footer>
       );
     }
   });
 
   var StandaloneRoomView = React.createClass({
-    mixins: [Backbone.Events],
+    mixins: [
+      Backbone.Events,
+      sharedMixins.RoomsAudioMixin
+    ],
 
     propTypes: {
       activeRoomStore:
         React.PropTypes.instanceOf(loop.store.ActiveRoomStore).isRequired,
       dispatcher: React.PropTypes.instanceOf(loop.Dispatcher).isRequired,
       helper: React.PropTypes.instanceOf(loop.shared.utils.Helper).isRequired
     },
 
--- a/browser/components/loop/test/shared/mixins_test.js
+++ b/browser/components/loop/test/shared/mixins_test.js
@@ -7,16 +7,17 @@
 
 var expect = chai.expect;
 
 describe("loop.shared.mixins", function() {
   "use strict";
 
   var sandbox;
   var sharedMixins = loop.shared.mixins;
+  var ROOM_STATES = loop.store.ROOM_STATES;
 
   beforeEach(function() {
     sandbox = sinon.sandbox.create();
   });
 
   afterEach(function() {
     sandbox.restore();
   });
@@ -209,9 +210,86 @@ describe("loop.shared.mixins", function(
       sinon.assert.calledOnce(navigator.mozLoop.getAudioBlob);
       sinon.assert.calledWithExactly(navigator.mozLoop.getAudioBlob,
                                      "failure", sinon.match.func);
       sinon.assert.calledOnce(fakeAudio.play);
       expect(fakeAudio.loop).to.equal(false);
     });
   });
 
+  describe("loop.shared.mixins.RoomsAudioMixin", function() {
+    var view, fakeAudioMixin, TestComp, comp;
+
+    function createTestComponent(initialState) {
+      var TestComp = React.createClass({
+        mixins: [loop.shared.mixins.RoomsAudioMixin],
+        render: function() {
+          return React.DOM.div();
+        },
+
+        getInitialState: function() {
+          return { roomState: initialState};
+        }
+      });
+
+      var renderedComp = TestUtils.renderIntoDocument(TestComp());
+      sandbox.stub(renderedComp, "play");
+      return renderedComp;
+    }
+
+    beforeEach(function() {
+    });
+
+    it("should play a sound when the local user joins the room", function() {
+      comp = createTestComponent(ROOM_STATES.INIT);
+
+      comp.setState({roomState: ROOM_STATES.SESSION_CONNECTED});
+
+      sinon.assert.calledOnce(comp.play);
+      sinon.assert.calledWithExactly(comp.play, "room-joined");
+    });
+
+    it("should play a sound when another user joins the room", function() {
+      comp = createTestComponent(ROOM_STATES.SESSION_CONNECTED);
+
+      comp.setState({roomState: ROOM_STATES.HAS_PARTICIPANTS});
+
+      sinon.assert.calledOnce(comp.play);
+      sinon.assert.calledWithExactly(comp.play, "room-joined-in");
+    });
+
+    it("should play a sound when another user leaves the room", function() {
+      comp = createTestComponent(ROOM_STATES.HAS_PARTICIPANTS);
+
+      comp.setState({roomState: ROOM_STATES.SESSION_CONNECTED});
+
+      sinon.assert.calledOnce(comp.play);
+      sinon.assert.calledWithExactly(comp.play, "room-left");
+    });
+
+    it("should play a sound when the local user leaves the room", function() {
+      comp = createTestComponent(ROOM_STATES.HAS_PARTICIPANTS);
+
+      comp.setState({roomState: ROOM_STATES.READY});
+
+      sinon.assert.calledOnce(comp.play);
+      sinon.assert.calledWithExactly(comp.play, "room-left");
+    });
+
+    it("should play a sound when if there is a failure", function() {
+      comp = createTestComponent(ROOM_STATES.HAS_PARTICIPANTS);
+
+      comp.setState({roomState: ROOM_STATES.FAILED});
+
+      sinon.assert.calledOnce(comp.play);
+      sinon.assert.calledWithExactly(comp.play, "failure");
+    });
+
+    it("should play a sound when if the room is full", function() {
+      comp = createTestComponent(ROOM_STATES.READY);
+
+      comp.setState({roomState: ROOM_STATES.FULL});
+
+      sinon.assert.calledOnce(comp.play);
+      sinon.assert.calledWithExactly(comp.play, "failure");
+    });
+  });
 });