Bug 1074666 - Part 3 Play a sound when a participant joined a room. Updated by pkerr,r=Standard8 a=lsblakk
authorMike de Boer <mdeboer@mozilla.com>
Mon, 17 Nov 2014 13:28:59 +0000
changeset 233967 ca2730f13b5ca582fabefcaded9abedc3c3c9867
parent 233966 979e6f0add7ccd42e53bb86f717000807f58bc8f
child 233968 9a750b1e767524b3ebf0359b9117a05131f6458c
push id4187
push userbhearsum@mozilla.com
push dateFri, 28 Nov 2014 15:29:12 +0000
treeherdermozilla-beta@f23cc6a30c11 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersStandard8, lsblakk
bugs1074666
milestone35.0a2
Bug 1074666 - Part 3 Play a sound when a participant joined a room. Updated by pkerr,r=Standard8 a=lsblakk
browser/base/content/browser-loop.js
browser/components/loop/LoopRooms.jsm
browser/components/loop/MozLoopService.jsm
browser/components/loop/content/shared/js/mixins.js
browser/components/loop/content/shared/sounds/room-joined.ogg
browser/components/loop/jar.mn
browser/components/loop/test/desktop-local/conversation_test.js
browser/components/loop/test/shared/mixins_test.js
--- a/browser/base/content/browser-loop.js
+++ b/browser/base/content/browser-loop.js
@@ -6,17 +6,16 @@
 let LoopUI;
 
 XPCOMUtils.defineLazyModuleGetter(this, "injectLoopAPI", "resource:///modules/loop/MozLoopAPI.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "MozLoopService", "resource:///modules/loop/MozLoopService.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "PanelFrame", "resource:///modules/PanelFrame.jsm");
 
 
 (function() {
-
   LoopUI = {
     get toolbarButton() {
       delete this.toolbarButton;
       return this.toolbarButton = CustomizableUI.getWidget("loop-button-throttled").forWindow(window);
     },
 
     /**
      * Opens the panel for Loop and sizes it appropriately.
@@ -84,10 +83,28 @@ XPCOMUtils.defineLazyModuleGetter(this, 
         state = "active";
       } else if (MozLoopService.doNotDisturb) {
         state = "disabled";
       } else if (MozLoopService.roomsParticipantsCount > 0) {
         state = "active";
       }
       this.toolbarButton.node.setAttribute("state", state);
     },
+
+    /**
+     * Play a sound in this window IF there's no sound playing yet.
+     *
+     * @param {String} name Name of the sound, like 'ringtone' or 'room-joined'
+     */
+    playSound: function(name) {
+      if (this.ActiveSound || MozLoopService.doNotDisturb) {
+        return;
+      }
+
+      this.activeSound = new window.Audio();
+      this.activeSound.src = `chrome://browser/content/loop/shared/sounds/${name}.ogg`;
+      this.activeSound.load();
+      this.activeSound.play();
+
+      this.activeSound.addEventListener("ended", () => this.activeSound = undefined, false);
+    },
   };
 })();
--- a/browser/components/loop/LoopRooms.jsm
+++ b/browser/components/loop/LoopRooms.jsm
@@ -51,17 +51,21 @@ const extend = function(target, source) 
  *
  * @param {Object} room        A room object that contains a list of current participants
  * @param {Object} participant Participant to check if it's already there
  * @returns {Boolean} TRUE when the participant is already a member of the room,
  *                    FALSE when it's not.
  */
 const containsParticipant = function(room, participant) {
   for (let user of room.participants) {
-    if (user.roomConnectionId == participant.roomConnectionId) {
+    // XXX until a bug 1100318 is implemented and deployed,
+    // we need to check the "id" field here as well - roomConnectionId is the
+    // official value for the interface.
+    if (user.roomConnectionId == participant.roomConnectionId &&
+        user.id == participant.id) {
       return true;
     }
   }
   return false;
 };
 
 /**
  * Compares the list of participants of the room currently in the cache and an
@@ -170,16 +174,20 @@ let LoopRoomsInternal = {
       }
 
       for (let room of roomsList) {
         // See if we already have this room in our cache.
         let orig = this.rooms.get(room.roomToken);
         if (orig) {
           checkForParticipantsUpdate(orig, room);
         }
+        // Remove the `currSize` for posterity.
+        if ("currSize" in room) {
+          delete room.currSize;
+        }
         this.rooms.set(room.roomToken, room);
         // When a version is specified, all the data is already provided by this
         // request.
         if (version) {
           eventEmitter.emit("update", room);
           eventEmitter.emit("update" + ":" + room.roomToken, room);
         } else {
           // Next, request the detailed information for each room. If the request
@@ -219,21 +227,16 @@ let LoopRoomsInternal = {
 
     MozLoopService.hawkRequest(this.sessionType, "/rooms/" + encodeURIComponent(roomToken), "GET")
       .then(response => {
         let data = JSON.parse(response.body);
 
         room.roomToken = roomToken;
         checkForParticipantsUpdate(room, data);
         extend(room, data);
-
-        // Remove the `currSize` for posterity.
-        if ("currSize" in room) {
-          delete room.currSize;
-        }
         this.rooms.set(roomToken, room);
 
         let eventName = !needsUpdate ? "update" : "add";
         eventEmitter.emit(eventName, room);
         eventEmitter.emit(eventName + ":" + roomToken, room);
         callback(null, room);
       }, err => callback(err)).catch(err => callback(err));
   },
--- a/browser/components/loop/MozLoopService.jsm
+++ b/browser/components/loop/MozLoopService.jsm
@@ -81,16 +81,20 @@ XPCOMUtils.defineLazyModuleGetter(this, 
 XPCOMUtils.defineLazyServiceGetter(this, "uuidgen",
                                    "@mozilla.org/uuid-generator;1",
                                    "nsIUUIDGenerator");
 
 XPCOMUtils.defineLazyServiceGetter(this, "gDNSService",
                                    "@mozilla.org/network/dns-service;1",
                                    "nsIDNSService");
 
+XPCOMUtils.defineLazyServiceGetter(this, "gWM",
+                                   "@mozilla.org/appshell/window-mediator;1",
+                                   "nsIWindowMediator");
+
 // Create a new instance of the ConsoleAPI so we can control the maxLogLevel with a pref.
 XPCOMUtils.defineLazyGetter(this, "log", () => {
   let ConsoleAPI = Cu.import("resource://gre/modules/devtools/Console.jsm", {}).ConsoleAPI;
   let consoleOptions = {
     maxLogLevel: Services.prefs.getCharPref(PREF_LOG_LEVEL).toLowerCase(),
     prefix: "Loop",
   };
   return new ConsoleAPI(consoleOptions);
@@ -1003,16 +1007,28 @@ this.MozLoopService = {
 
     // The Loop toolbar button should change icon when the room participant count
     // changes from 0 to something.
     const onRoomsChange = () => {
       MozLoopServiceInternal.notifyStatusChanged();
     };
     LoopRooms.on("add", onRoomsChange);
     LoopRooms.on("update", onRoomsChange);
+    LoopRooms.on("joined", (e, roomToken, participant) => {
+      // Don't alert if we're in the doNotDisturb mode, or the participant
+      // is the owner - the content code deals with the rest of the sounds.
+      if (MozLoopServiceInternal.doNotDisturb || participant.owner) {
+        return;
+      }
+
+      let window = gWM.getMostRecentWindow("navigator:browser");
+      if (window) {
+        window.LoopUI.playSound("room-joined");
+      }
+    });
 
     // If expiresTime is not in the future and the user hasn't
     // previously authenticated then skip registration.
     if (!MozLoopServiceInternal.urlExpiryTimeIsInFuture() &&
         !MozLoopServiceInternal.fxAOAuthTokenData) {
       return Promise.resolve("registration not needed");
     }
 
--- a/browser/components/loop/content/shared/js/mixins.js
+++ b/browser/components/loop/content/shared/js/mixins.js
@@ -149,16 +149,20 @@ loop.shared.mixins = (function() {
     },
 
     /**
      * Starts playing an audio file, stopping any audio that is already in progress.
      *
      * @param {String} name The filename to play (excluding the extension).
      */
     play: function(name, options) {
+      if (this._isLoopDesktop() && rootObject.navigator.mozLoop.doNotDisturb) {
+        return;
+      }
+
       options = options || {};
       options.loop = options.loop || false;
 
       this._ensureAudioStopped();
       this._getAudioBlob(name, function(error, blob) {
         if (error) {
           console.error(error);
           return;
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..5e952291f167014246bbc28270c6897db39fe004
GIT binary patch
literal 16312
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
--- a/browser/components/loop/jar.mn
+++ b/browser/components/loop/jar.mn
@@ -81,16 +81,17 @@ browser.jar:
   content/browser/loop/shared/libs/jquery-2.1.0.js    (content/shared/libs/jquery-2.1.0.js)
   content/browser/loop/shared/libs/backbone-1.1.2.js  (content/shared/libs/backbone-1.1.2.js)
 
   # Shared sounds
   content/browser/loop/shared/sounds/ringtone.ogg     (content/shared/sounds/ringtone.ogg)
   content/browser/loop/shared/sounds/connecting.ogg   (content/shared/sounds/connecting.ogg)
   content/browser/loop/shared/sounds/connected.ogg    (content/shared/sounds/connected.ogg)
   content/browser/loop/shared/sounds/terminated.ogg   (content/shared/sounds/terminated.ogg)
+  content/browser/loop/shared/sounds/room-joined.ogg  (content/shared/sounds/room-joined.ogg)
   content/browser/loop/shared/sounds/failure.ogg      (content/shared/sounds/failure.ogg)
 
   # Partner SDK assets
   content/browser/loop/libs/sdk.js                                                    (content/shared/libs/sdk.js)
   content/browser/loop/sdk-content/css/ot.css                                 (content/shared/libs/sdk-content/css/ot.css)
   content/browser/loop/sdk-content/js/dynamic_config.min.js                   (content/shared/libs/sdk-content/js/dynamic_config.min.js)
   content/browser/loop/sdk-content/images/rtc/access-denied-chrome.png        (content/shared/libs/sdk-content/images/rtc/access-denied-chrome.png)
   content/browser/loop/sdk-content/images/rtc/access-denied-copy-firefox.png  (content/shared/libs/sdk-content/images/rtc/access-denied-copy-firefox.png)
--- a/browser/components/loop/test/desktop-local/conversation_test.js
+++ b/browser/components/loop/test/desktop-local/conversation_test.js
@@ -912,16 +912,17 @@ describe("loop.conversation", function()
     var view, fakeAudio;
 
     beforeEach(function() {
       fakeAudio = {
         play: sinon.spy(),
         pause: sinon.spy(),
         removeAttribute: sinon.spy()
       };
+      navigator.mozLoop.doNotDisturb = false;
       sandbox.stub(window, "Audio").returns(fakeAudio);
 
       view = TestUtils.renderIntoDocument(
         loop.conversation.GenericFailureView({
           cancelCall: function() {}
         })
       );
     });
--- a/browser/components/loop/test/shared/mixins_test.js
+++ b/browser/components/loop/test/shared/mixins_test.js
@@ -161,9 +161,57 @@ describe("loop.shared.mixins", function(
       function() {
         setupFakeVisibilityEventDispatcher({target: {hidden: true}});
 
         comp = TestUtils.renderIntoDocument(TestComp());
 
         sinon.assert.calledOnce(onDocumentHiddenStub);
       });
   });
+
+  describe("loop.shared.mixins.AudioMixin", function() {
+    var view, fakeAudio, TestComp;
+
+    beforeEach(function() {
+      navigator.mozLoop = {
+        doNotDisturb: true,
+        getAudioBlob: sinon.spy(function(name, callback) {
+          callback(null, new Blob([new ArrayBuffer(10)], {type: 'audio/ogg'}));
+        })
+      };
+
+      fakeAudio = {
+        play: sinon.spy(),
+        pause: sinon.spy(),
+        removeAttribute: sinon.spy()
+      };
+      sandbox.stub(window, "Audio").returns(fakeAudio);
+
+      TestComp = React.createClass({
+        mixins: [loop.shared.mixins.AudioMixin],
+        componentDidMount: function() {
+          this.play("failure");
+        },
+        render: function() {
+          return React.DOM.div();
+        }
+      });
+
+    });
+
+    it("should not play a failure sound when doNotDisturb true", function() {
+      view = TestUtils.renderIntoDocument(TestComp());
+      sinon.assert.notCalled(navigator.mozLoop.getAudioBlob);
+      sinon.assert.notCalled(fakeAudio.play);
+    });
+
+    it("should play a failure sound, once", function() {
+      navigator.mozLoop.doNotDisturb = false;
+      view = TestUtils.renderIntoDocument(TestComp());
+      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);
+    });
+  });
+
 });