Bug 1180179 - Part 4. Use the shared media layout component in Loop's room views. r=mikedeboer
authorMark Banner <standard8@mozilla.com>
Mon, 27 Jul 2015 12:58:02 +0200
changeset 286373 4791b25ea75bfe1ac5259ead3036a03d75824635
parent 286372 0ccdbc708362b19f8656f3c2429675b661b43e5d
child 286374 21ca97268bae2d746e09ad6c612f4fbf3df0fe6e
child 286398 1372fb8872a16321fead0614ba5ede0ac194cbf3
push id5067
push userraliiev@mozilla.com
push dateMon, 21 Sep 2015 14:04:52 +0000
treeherdermozilla-beta@14221ffe5b2f [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmikedeboer
bugs1180179
milestone42.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 1180179 - Part 4. Use the shared media layout component in Loop's room views. r=mikedeboer
browser/components/loop/content/js/roomViews.js
browser/components/loop/content/js/roomViews.jsx
browser/components/loop/content/shared/css/conversation.css
browser/components/loop/content/shared/js/textChatView.js
browser/components/loop/content/shared/js/textChatView.jsx
browser/components/loop/content/shared/js/views.js
browser/components/loop/content/shared/js/views.jsx
browser/components/loop/test/desktop-local/index.html
browser/components/loop/test/desktop-local/roomViews_test.js
browser/components/loop/test/functional/test_1_browser_call.py
browser/components/loop/ui/ui-showcase.js
browser/components/loop/ui/ui-showcase.jsx
--- a/browser/components/loop/content/js/roomViews.js
+++ b/browser/components/loop/content/js/roomViews.js
@@ -660,29 +660,29 @@ loop.roomViews = (function(mozL10n) {
 
     /**
      * Should we render a visual cue to the user (e.g. a spinner) that a local
      * stream is on its way from the camera?
      *
      * @returns {boolean}
      * @private
      */
-    _shouldRenderLocalLoading: function () {
+    _isLocalLoading: function () {
       return this.state.roomState === ROOM_STATES.MEDIA_WAIT &&
              !this.state.localSrcVideoObject;
     },
 
     /**
      * Should we render a visual cue to the user (e.g. a spinner) that a remote
      * stream is on its way from the other user?
      *
      * @returns {boolean}
      * @private
      */
-    _shouldRenderRemoteLoading: function() {
+    _isRemoteLoading: function() {
       return !!(this.state.roomState === ROOM_STATES.HAS_PARTICIPANTS &&
                 !this.state.remoteSrcVideoObject &&
                 !this.state.mediaConnected);
     },
 
     handleAddContextClick: function() {
       this.setState({ showEditContext: true });
     },
@@ -736,73 +736,64 @@ loop.roomViews = (function(mozL10n) {
           );
         }
         case ROOM_STATES.ENDED: {
           // When conversation ended we either display a feedback form or
           // close the window. This is decided in the AppControllerView.
           return null;
         }
         default: {
-
           return (
-            React.createElement("div", {className: "room-conversation-wrapper"}, 
-              React.createElement("div", {className: "video-layout-wrapper"}, 
-                React.createElement("div", {className: "conversation room-conversation"}, 
-                  React.createElement("div", {className: "media nested"}, 
-                    React.createElement(DesktopRoomInvitationView, {
-                      dispatcher: this.props.dispatcher, 
-                      error: this.state.error, 
-                      mozLoop: this.props.mozLoop, 
-                      onAddContextClick: this.handleAddContextClick, 
-                      onEditContextClose: this.handleEditContextClose, 
-                      roomData: roomData, 
-                      savingContext: this.state.savingContext, 
-                      show: shouldRenderInvitationOverlay, 
-                      showEditContext: shouldRenderInvitationOverlay && shouldRenderEditContextView, 
-                      socialShareProviders: this.state.socialShareProviders}), 
-                    React.createElement("div", {className: "video_wrapper remote_wrapper"}, 
-                      React.createElement("div", {className: "video_inner remote focus-stream"}, 
-                        React.createElement(sharedViews.MediaView, {displayAvatar: !this.shouldRenderRemoteVideo(), 
-                          isLoading: this._shouldRenderRemoteLoading(), 
-                          mediaType: "remote", 
-                          posterUrl: this.props.remotePosterUrl, 
-                          srcVideoObject: this.state.remoteSrcVideoObject})
-                      )
-                    ), 
-                    React.createElement("div", {className: localStreamClasses}, 
-                      React.createElement(sharedViews.MediaView, {displayAvatar: this.state.videoMuted, 
-                        isLoading: this._shouldRenderLocalLoading(), 
-                        mediaType: "local", 
-                        posterUrl: this.props.localPosterUrl, 
-                        srcVideoObject: this.state.localSrcVideoObject})
-                    ), 
-                    React.createElement(DesktopRoomEditContextView, {
-                      dispatcher: this.props.dispatcher, 
-                      error: this.state.error, 
-                      mozLoop: this.props.mozLoop, 
-                      onClose: this.handleEditContextClose, 
-                      roomData: roomData, 
-                      savingContext: this.state.savingContext, 
-                      show: !shouldRenderInvitationOverlay && shouldRenderEditContextView})
-                  ), 
-                  React.createElement(sharedViews.ConversationToolbar, {
-                    audio: {enabled: !this.state.audioMuted, visible: true}, 
-                    dispatcher: this.props.dispatcher, 
-                    edit: { visible: this.state.contextEnabled, enabled: !this.state.showEditContext}, 
-                    hangup: this.leaveRoom, 
-                    onEditClick: this.handleEditContextClick, 
-                    publishStream: this.publishStream, 
-                    screenShare: screenShareData, 
-                    video: {enabled: !this.state.videoMuted, visible: true}})
-                )
+            React.createElement("div", {className: "room-conversation-wrapper desktop-room-wrapper"}, 
+              React.createElement(sharedViews.MediaLayoutView, {
+                dispatcher: this.props.dispatcher, 
+                displayScreenShare: false, 
+                isLocalLoading: this._isLocalLoading(), 
+                isRemoteLoading: this._isRemoteLoading(), 
+                isScreenShareLoading: false, 
+                localPosterUrl: this.props.localPosterUrl, 
+                localSrcVideoObject: this.state.localSrcVideoObject, 
+                localVideoMuted: this.state.videoMuted, 
+                matchMedia: this.state.matchMedia || window.matchMedia.bind(window), 
+                remotePosterUrl: this.props.remotePosterUrl, 
+                remoteSrcVideoObject: this.state.remoteSrcVideoObject, 
+                renderRemoteVideo: this.shouldRenderRemoteVideo(), 
+                screenSharePosterUrl: null, 
+                screenShareVideoObject: this.state.screenShareVideoObject, 
+                showContextRoomName: false, 
+                useDesktopPaths: true}, 
+                React.createElement(DesktopRoomInvitationView, {
+                  dispatcher: this.props.dispatcher, 
+                  error: this.state.error, 
+                  mozLoop: this.props.mozLoop, 
+                  onAddContextClick: this.handleAddContextClick, 
+                  onEditContextClose: this.handleEditContextClose, 
+                  roomData: roomData, 
+                  savingContext: this.state.savingContext, 
+                  show: shouldRenderInvitationOverlay, 
+                  showEditContext: shouldRenderInvitationOverlay && shouldRenderEditContextView, 
+                  socialShareProviders: this.state.socialShareProviders}), 
+                React.createElement(DesktopRoomEditContextView, {
+                  dispatcher: this.props.dispatcher, 
+                  error: this.state.error, 
+                  mozLoop: this.props.mozLoop, 
+                  onClose: this.handleEditContextClose, 
+                  roomData: roomData, 
+                  savingContext: this.state.savingContext, 
+                  show: !shouldRenderInvitationOverlay && shouldRenderEditContextView})
               ), 
-              React.createElement(sharedViews.chat.TextChatView, {
+              React.createElement(sharedViews.ConversationToolbar, {
+                audio: {enabled: !this.state.audioMuted, visible: true}, 
                 dispatcher: this.props.dispatcher, 
-                showRoomName: false, 
-                useDesktopPaths: true})
+                edit: { visible: this.state.contextEnabled, enabled: !this.state.showEditContext}, 
+                hangup: this.leaveRoom, 
+                onEditClick: this.handleEditContextClick, 
+                publishStream: this.publishStream, 
+                screenShare: screenShareData, 
+                video: {enabled: !this.state.videoMuted, visible: true}})
             )
           );
         }
       }
     }
   });
 
   return {
--- a/browser/components/loop/content/js/roomViews.jsx
+++ b/browser/components/loop/content/js/roomViews.jsx
@@ -660,29 +660,29 @@ loop.roomViews = (function(mozL10n) {
 
     /**
      * Should we render a visual cue to the user (e.g. a spinner) that a local
      * stream is on its way from the camera?
      *
      * @returns {boolean}
      * @private
      */
-    _shouldRenderLocalLoading: function () {
+    _isLocalLoading: function () {
       return this.state.roomState === ROOM_STATES.MEDIA_WAIT &&
              !this.state.localSrcVideoObject;
     },
 
     /**
      * Should we render a visual cue to the user (e.g. a spinner) that a remote
      * stream is on its way from the other user?
      *
      * @returns {boolean}
      * @private
      */
-    _shouldRenderRemoteLoading: function() {
+    _isRemoteLoading: function() {
       return !!(this.state.roomState === ROOM_STATES.HAS_PARTICIPANTS &&
                 !this.state.remoteSrcVideoObject &&
                 !this.state.mediaConnected);
     },
 
     handleAddContextClick: function() {
       this.setState({ showEditContext: true });
     },
@@ -736,73 +736,64 @@ loop.roomViews = (function(mozL10n) {
           );
         }
         case ROOM_STATES.ENDED: {
           // When conversation ended we either display a feedback form or
           // close the window. This is decided in the AppControllerView.
           return null;
         }
         default: {
-
           return (
-            <div className="room-conversation-wrapper">
-              <div className="video-layout-wrapper">
-                <div className="conversation room-conversation">
-                  <div className="media nested">
-                    <DesktopRoomInvitationView
-                      dispatcher={this.props.dispatcher}
-                      error={this.state.error}
-                      mozLoop={this.props.mozLoop}
-                      onAddContextClick={this.handleAddContextClick}
-                      onEditContextClose={this.handleEditContextClose}
-                      roomData={roomData}
-                      savingContext={this.state.savingContext}
-                      show={shouldRenderInvitationOverlay}
-                      showEditContext={shouldRenderInvitationOverlay && shouldRenderEditContextView}
-                      socialShareProviders={this.state.socialShareProviders} />
-                    <div className="video_wrapper remote_wrapper">
-                      <div className="video_inner remote focus-stream">
-                        <sharedViews.MediaView displayAvatar={!this.shouldRenderRemoteVideo()}
-                          isLoading={this._shouldRenderRemoteLoading()}
-                          mediaType="remote"
-                          posterUrl={this.props.remotePosterUrl}
-                          srcVideoObject={this.state.remoteSrcVideoObject} />
-                      </div>
-                    </div>
-                    <div className={localStreamClasses}>
-                      <sharedViews.MediaView displayAvatar={this.state.videoMuted}
-                        isLoading={this._shouldRenderLocalLoading()}
-                        mediaType="local"
-                        posterUrl={this.props.localPosterUrl}
-                        srcVideoObject={this.state.localSrcVideoObject} />
-                    </div>
-                    <DesktopRoomEditContextView
-                      dispatcher={this.props.dispatcher}
-                      error={this.state.error}
-                      mozLoop={this.props.mozLoop}
-                      onClose={this.handleEditContextClose}
-                      roomData={roomData}
-                      savingContext={this.state.savingContext}
-                      show={!shouldRenderInvitationOverlay && shouldRenderEditContextView} />
-                  </div>
-                  <sharedViews.ConversationToolbar
-                    audio={{enabled: !this.state.audioMuted, visible: true}}
-                    dispatcher={this.props.dispatcher}
-                    edit={{ visible: this.state.contextEnabled, enabled: !this.state.showEditContext }}
-                    hangup={this.leaveRoom}
-                    onEditClick={this.handleEditContextClick}
-                    publishStream={this.publishStream}
-                    screenShare={screenShareData}
-                    video={{enabled: !this.state.videoMuted, visible: true}} />
-                </div>
-              </div>
-              <sharedViews.chat.TextChatView
+            <div className="room-conversation-wrapper desktop-room-wrapper">
+              <sharedViews.MediaLayoutView
                 dispatcher={this.props.dispatcher}
-                showRoomName={false}
-                useDesktopPaths={true} />
+                displayScreenShare={false}
+                isLocalLoading={this._isLocalLoading()}
+                isRemoteLoading={this._isRemoteLoading()}
+                isScreenShareLoading={false}
+                localPosterUrl={this.props.localPosterUrl}
+                localSrcVideoObject={this.state.localSrcVideoObject}
+                localVideoMuted={this.state.videoMuted}
+                matchMedia={this.state.matchMedia || window.matchMedia.bind(window)}
+                remotePosterUrl={this.props.remotePosterUrl}
+                remoteSrcVideoObject={this.state.remoteSrcVideoObject}
+                renderRemoteVideo={this.shouldRenderRemoteVideo()}
+                screenSharePosterUrl={null}
+                screenShareVideoObject={this.state.screenShareVideoObject}
+                showContextRoomName={false}
+                useDesktopPaths={true}>
+                <DesktopRoomInvitationView
+                  dispatcher={this.props.dispatcher}
+                  error={this.state.error}
+                  mozLoop={this.props.mozLoop}
+                  onAddContextClick={this.handleAddContextClick}
+                  onEditContextClose={this.handleEditContextClose}
+                  roomData={roomData}
+                  savingContext={this.state.savingContext}
+                  show={shouldRenderInvitationOverlay}
+                  showEditContext={shouldRenderInvitationOverlay && shouldRenderEditContextView}
+                  socialShareProviders={this.state.socialShareProviders} />
+                <DesktopRoomEditContextView
+                  dispatcher={this.props.dispatcher}
+                  error={this.state.error}
+                  mozLoop={this.props.mozLoop}
+                  onClose={this.handleEditContextClose}
+                  roomData={roomData}
+                  savingContext={this.state.savingContext}
+                  show={!shouldRenderInvitationOverlay && shouldRenderEditContextView} />
+              </sharedViews.MediaLayoutView>
+              <sharedViews.ConversationToolbar
+                audio={{enabled: !this.state.audioMuted, visible: true}}
+                dispatcher={this.props.dispatcher}
+                edit={{ visible: this.state.contextEnabled, enabled: !this.state.showEditContext }}
+                hangup={this.leaveRoom}
+                onEditClick={this.handleEditContextClick}
+                publishStream={this.publishStream}
+                screenShare={screenShareData}
+                video={{enabled: !this.state.videoMuted, visible: true}} />
             </div>
           );
         }
       }
     }
   });
 
   return {
--- a/browser/components/loop/content/shared/css/conversation.css
+++ b/browser/components/loop/content/shared/css/conversation.css
@@ -499,38 +499,16 @@
 
 .feedback .info {
   display: block;
   font-size: 10px;
   color: #CCC;
   text-align: center;
 }
 
-.fx-embedded .local-stream {
-  position: absolute;
-  right: 3px;
-  bottom: 5px;
-  /* next two lines are workaround for lack of object-fit; see bug 1020445 */
-  max-width: 140px;
-  width: 30%;
-  height: 28%;
-  max-height: 105px;
-}
-
-.fx-embedded .local-stream.room-preview {
-  top: 0px;
-  left: 0px;
-  right: 0px;
-  bottom: 0px;
-  height: 100%;
-  width: 100%;
-  max-width: none;
-  max-height: none;
-}
-
 .conversation .media.nested .focus-stream {
   display: inline-block;
   position: absolute; /* workaround for lack of object-fit; see bug 1020445 */
   width: 100%;
   top: 0;
   bottom: 0;
   left: 0;
   right: 0;
@@ -592,20 +570,16 @@
   z-index: 1;
 }
 
 .remote > .avatar {
   /* make visually distinct from local avatar */
   opacity: 0.25;
 }
 
-.fx-embedded .media.nested {
-  min-height: 200px;
-}
-
 .fx-embedded-call-identifier {
   display: inline;
   width: 100%;
   padding: 1.2em;
 }
 
 .fx-embedded-call-identifier-item {
   height: 50px;
@@ -671,17 +645,18 @@
 
 /* Force full height on all parents up to the video elements
  * this way we can ensure the aspect ratio and use height 100%
  * on the video element
  * */
 html, .fx-embedded, #main,
 .video-layout-wrapper,
 .conversation,
-.desktop-call-wrapper {
+.desktop-call-wrapper,
+.desktop-room-wrapper {
   height: 100%;
 }
 
 /* We use 641px rather than 640, as min-width and max-width are inclusive */
 @media screen and (min-width: 641px) {
   .standalone .conversation .conversation-toolbar {
     position: absolute;
     bottom: 0;
@@ -931,33 +906,32 @@ body[platform="win"] .share-service-drop
   background-color: #E8F6FE;
 }
 
 .room-context {
   background: rgba(0,0,0,.8);
   border-top: 2px solid #444;
   border-bottom: 2px solid #444;
   padding: .5rem;
-  max-height: 400px;
   position: absolute;
   left: 0;
   bottom: 0;
   width: 100%;
   /* Stretch to the maximum available space whilst not covering the conversation
      toolbar (26px). */
   height: calc(100% - 26px);
   font-size: .9em;
   display: flex;
   flex-flow: column nowrap;
   align-content: flex-start;
   align-items: flex-start;
   overflow-x: hidden;
   overflow-y: auto;
   /* Make the context view float atop the video elements. */
-  z-index: 2;
+  z-index: 3;
 }
 
 .room-invitation-overlay .room-context {
   position: relative;
   left: auto;
   bottom: auto;
   flex: 0 1 auto;
   height: 100%;
@@ -1135,17 +1109,18 @@ html[dir="rtl"] .room-context-btn-close 
 }
 
 .media-wrapper.showing-local-streams.receiving-screen-share > .text-chat-view {
   /* When we're displaying the local streams, then we need to make the text
      chat view a bit shorter to give room. */
   height: calc(100% - 300px);
 }
 
-.desktop-call-wrapper > .media-layout > .media-wrapper > .text-chat-view {
+.desktop-call-wrapper > .media-layout > .media-wrapper > .text-chat-view,
+.desktop-room-wrapper > .media-layout > .media-wrapper > .text-chat-view {
   /* Account for height of .conversation-toolbar on desktop */
   /* When we change the toolbar in bug 1184559 we can remove this. */
   margin-top: 26px;
   height: calc(100% - 150px - 26px);
 }
 
 /* Temporarily slaved from .media-wrapper until we use it in more places
    to avoid affecting the conversation window on desktop. */
@@ -1207,32 +1182,32 @@ html[dir="rtl"] .room-context-btn-close 
   }
 
   .media-wrapper.receiving-screen-share > .focus-stream {
     height: 50%;
   }
 
   /* Temporarily slaved from .media-wrapper until we use it in more places
      to avoid affecting the conversation window on desktop. */
-  .media-wrapper > .text-chat-view > .text-chat-entries {
+  .text-chat-view > .text-chat-entries {
     /* 40px is the height of .text-chat-box. */
     height: calc(100% - 40px);
     width: 100%;
   }
 
   .media-wrapper > .text-chat-disabled > .text-chat-entries {
     /* When text chat is disabled, the entries box should be 100% height. */
     height: 100%;
   }
 
   .media-wrapper > .focus-stream > .local {
     /* Position over the remote video */
     position: absolute;
     /* Make sure its on top */
-    z-index: 1001;
+    z-index: 2;
     margin: 3px;
     right: 0;
     /* 29px is (30% of 50px high header) + (height toolbar (38px) +
        height footer (25px) - height header (50px)) */
     bottom: 0;
     width: 120px;
     height: 120px;
   }
@@ -1256,17 +1231,18 @@ html[dir="rtl"] .room-context-btn-close 
 
   .media-wrapper > .text-chat-view,
   .media-wrapper.showing-local-streams > .text-chat-view,
   .media-wrapper.showing-local-streams.receiving-screen-share > .text-chat-view {
     /* The remaining 30% that the .focus-stream doesn't use. */
     height: 30%;
   }
 
-  .desktop-call-wrapper > .media-layout > .media-wrapper > .text-chat-view {
+  .desktop-call-wrapper > .media-layout > .media-wrapper > .text-chat-view,
+  .desktop-room-wrapper > .media-layout > .media-wrapper > .text-chat-view {
     /* When we change the toolbar in bug 1184559 we can remove this. */
     /* Reset back to 0 for .conversation-toolbar override on desktop */
     margin-top: 0;
     /* This is temp, to echo the .media-wrapper > .text-chat-view above */
     height: 30%;
   }
 
   .media-wrapper.receiving-screen-share > .screen {
@@ -1311,42 +1287,43 @@ html[dir="rtl"] .room-context-btn-close 
 }
 
 /* e.g. very narrow widths similar to conversation window */
 @media screen and (max-width:300px) {
   .media-layout > .media-wrapper {
     flex-flow: column nowrap;
   }
 
-  .media-wrapper:not(.showing-remote-streams) > .remote {
-    display: none;
-  }
-
   .media-wrapper > .focus-stream > .local {
     position: absolute;
     right: 0;
     /* 30% is the height of the text chat. As we have a margin,
        we don't need to worry about any offset for a border */
     bottom: 0;
     margin: 3px;
     object-fit: contain;
     /* These make the avatar look reasonable and the local
        video not too big */
     width: 25%;
     height: 25%;
   }
 
-  .media-wrapper:not(.showing-remote-streams) > .local {
+  .media-wrapper:not(.showing-remote-streams) > .focus-stream > .no-video {
+    display: none;
+  }
+
+  .media-wrapper:not(.showing-remote-streams) > .focus-stream > .local {
     position: relative;
     margin: 0;
     right: auto;
     left: auto;
     bottom: auto;
     width: 100%;
     height: 100%;
+    background-color: black;
   }
 
   .media-wrapper > .focus-stream {
     flex: 1 1 auto;
     height: auto;
   }
 }
 
@@ -1478,43 +1455,16 @@ html[dir="rtl"] .standalone .room-conver
 }
 
 /* Text chat in styles */
 
 .text-chat-view {
   background: white;
 }
 
-/* Old styles to cope with rooms until we transition those to media-layout. */
-.fx-embedded .room-conversation-wrapper {
-  display: flex;
-  flex-flow: column nowrap;
-}
-
-.fx-embedded .room-conversation-wrapper > .video-layout-wrapper  {
-  flex: 1 1 auto;
-}
-
-.fx-embedded .room-conversation-wrapper > .text-chat-view {
-  flex: 1 0 auto;
-  display: flex;
-  flex-flow: column nowrap;
-}
-
-.fx-embedded .room-conversation-wrapper > .text-chat-view .text-chat-entries {
-  flex: 1 1 auto;
-  max-height: 120px;
-  min-height: 60px;
-}
-
-.fx-embedded .room-conversation-wrapper > .text-chat-view > .text-chat-entries-empty {
-  display: none;
-}
-
-
 .text-chat-box {
   flex: 0 0 auto;
   max-height: 40px;
   min-height: 40px;
   width: 100%;
 }
 
 .text-chat-entries {
@@ -1801,55 +1751,51 @@ html[dir="rtl"] .text-chat-entry.receive
   .standalone .media.nested {
     /* This forces the remote video stream to fit within wrapper's height */
     min-height: 0px;
   }
 }
 
 /* e.g. very narrow widths similar to conversation window */
 @media screen and (max-width:300px) {
-  /**
-   * XXX This section scoped to .media-layout until we have the desktop
-   * rooms working from the media-layout as well.
-   */
-  .media-layout .text-chat-view {
+  .text-chat-view {
     flex: 0 0 auto;
     display: flex;
     flex-flow: column nowrap;
     /* 120px max-height of .text-chat-entries plus 40px of .text-chat-box */
     max-height: 160px;
     /* 60px min-height of .text-chat-entries plus 40px of .text-chat-box */
     min-height: 100px;
     /* The !important is to override the values defined above which have more
        specificity when we fix bug 1184559, we should be able to remove it,
        but this should be tests first. */
     height: auto !important;
   }
 
-  .media-layout .text-chat-entries {
+  .text-chat-entries {
     /* The !important is to override the values defined above which have more
        specificity when we fix bug 1184559, we should be able to remove it,
        but this should be tests first. */
     flex: 1 1 auto !important;
     max-height: 120px;
     min-height: 60px;
   }
 
-  .media-layout .text-chat-entries-empty.text-chat-disabled {
+  .text-chat-entries-empty.text-chat-disabled {
     display: none;
   }
 
   /* When the text chat entries are not present, then hide the entries view
      and just show the chat box. */
-  .media-layout .text-chat-entries-empty {
+  .text-chat-entries-empty {
     max-height: 40px;
     min-height: 40px;
   }
 
-  .media-layout .text-chat-entries-empty > .text-chat-entries {
+  .text-chat-entries-empty > .text-chat-entries {
     display: none;
   }
 }
 
 .self-view-hidden-message {
   /* Not displayed by default; display is turned on elsewhere when the
    * self-view is actually hidden.
    */
--- a/browser/components/loop/content/shared/js/textChatView.js
+++ b/browser/components/loop/content/shared/js/textChatView.js
@@ -145,20 +145,17 @@ loop.shared.views.chat = (function(mozL1
       }
     },
 
     render: function() {
       /* Keep track of the last printed timestamp. */
       var lastTimestamp = 0;
 
       var entriesClasses = React.addons.classSet({
-        "text-chat-entries": true,
-        // XXX Only required until we get the desktop rooms on media-layout
-        // as well.
-        "text-chat-entries-empty": !this.props.messageList.length
+        "text-chat-entries": true
       });
 
       return (
         React.createElement("div", {className: entriesClasses}, 
           React.createElement("div", {className: "text-chat-scroller"}, 
             
               this.props.messageList.map(function(entry, i) {
                 if (entry.type === CHAT_MESSAGE_TYPES.SPECIAL) {
@@ -380,17 +377,17 @@ loop.shared.views.chat = (function(mozL1
             item.contentType !== CHAT_CONTENT_TYPES.ROOM_NAME;
         });
         hasNonSpecialMessages = !!messageList.length;
       }
 
       var textChatViewClasses = React.addons.classSet({
         "text-chat-view": true,
         "text-chat-disabled": !this.state.textChatEnabled,
-        "text-chat-entries-empty": !this.state.messageList.length
+        "text-chat-entries-empty": !messageList.length
       });
 
       return (
         React.createElement("div", {className: textChatViewClasses}, 
           React.createElement(TextChatEntriesView, {
             dispatcher: this.props.dispatcher, 
             messageList: messageList, 
             useDesktopPaths: this.props.useDesktopPaths}), 
--- a/browser/components/loop/content/shared/js/textChatView.jsx
+++ b/browser/components/loop/content/shared/js/textChatView.jsx
@@ -145,20 +145,17 @@ loop.shared.views.chat = (function(mozL1
       }
     },
 
     render: function() {
       /* Keep track of the last printed timestamp. */
       var lastTimestamp = 0;
 
       var entriesClasses = React.addons.classSet({
-        "text-chat-entries": true,
-        // XXX Only required until we get the desktop rooms on media-layout
-        // as well.
-        "text-chat-entries-empty": !this.props.messageList.length
+        "text-chat-entries": true
       });
 
       return (
         <div className={entriesClasses}>
           <div className="text-chat-scroller">
             {
               this.props.messageList.map(function(entry, i) {
                 if (entry.type === CHAT_MESSAGE_TYPES.SPECIAL) {
@@ -380,17 +377,17 @@ loop.shared.views.chat = (function(mozL1
             item.contentType !== CHAT_CONTENT_TYPES.ROOM_NAME;
         });
         hasNonSpecialMessages = !!messageList.length;
       }
 
       var textChatViewClasses = React.addons.classSet({
         "text-chat-view": true,
         "text-chat-disabled": !this.state.textChatEnabled,
-        "text-chat-entries-empty": !this.state.messageList.length
+        "text-chat-entries-empty": !messageList.length
       });
 
       return (
         <div className={textChatViewClasses}>
           <TextChatEntriesView
             dispatcher={this.props.dispatcher}
             messageList={messageList}
             useDesktopPaths={this.props.useDesktopPaths} />
--- a/browser/components/loop/content/shared/js/views.js
+++ b/browser/components/loop/content/shared/js/views.js
@@ -941,16 +941,17 @@ loop.shared.views = (function(_, mozL10n
                {className: this.props.mediaType + "-video", 
                muted: true}))
       );
     }
   });
 
   var MediaLayoutView = React.createClass({displayName: "MediaLayoutView",
     propTypes: {
+      children: React.PropTypes.node,
       dispatcher: React.PropTypes.instanceOf(loop.Dispatcher).isRequired,
       displayScreenShare: React.PropTypes.bool.isRequired,
       isLocalLoading: React.PropTypes.bool.isRequired,
       isRemoteLoading: React.PropTypes.bool.isRequired,
       isScreenShareLoading: React.PropTypes.bool.isRequired,
       // The poster URLs are for UI-showcase testing and development.
       localPosterUrl: React.PropTypes.string,
       localSrcVideoObject: React.PropTypes.object,
@@ -1049,17 +1050,18 @@ loop.shared.views = (function(_, mozL10n
             ), 
             React.createElement("div", {className: remoteStreamClasses}, 
               React.createElement(MediaView, {displayAvatar: !this.props.renderRemoteVideo, 
                 isLoading: this.props.isRemoteLoading, 
                 mediaType: "remote", 
                 posterUrl: this.props.remotePosterUrl, 
                 srcVideoObject: this.props.remoteSrcVideoObject}), 
                this.state.localMediaAboslutelyPositioned ?
-                this.renderLocalVideo() : null
+                this.renderLocalVideo() : null, 
+               this.props.children
             ), 
             React.createElement("div", {className: screenShareStreamClasses}, 
               React.createElement(MediaView, {displayAvatar: false, 
                 isLoading: this.props.isScreenShareLoading, 
                 mediaType: "screen-share", 
                 posterUrl: this.props.screenSharePosterUrl, 
                 srcVideoObject: this.props.screenShareVideoObject})
             ), 
--- a/browser/components/loop/content/shared/js/views.jsx
+++ b/browser/components/loop/content/shared/js/views.jsx
@@ -941,16 +941,17 @@ loop.shared.views = (function(_, mozL10n
                className={this.props.mediaType + "-video"}
                muted />
       );
     }
   });
 
   var MediaLayoutView = React.createClass({
     propTypes: {
+      children: React.PropTypes.node,
       dispatcher: React.PropTypes.instanceOf(loop.Dispatcher).isRequired,
       displayScreenShare: React.PropTypes.bool.isRequired,
       isLocalLoading: React.PropTypes.bool.isRequired,
       isRemoteLoading: React.PropTypes.bool.isRequired,
       isScreenShareLoading: React.PropTypes.bool.isRequired,
       // The poster URLs are for UI-showcase testing and development.
       localPosterUrl: React.PropTypes.string,
       localSrcVideoObject: React.PropTypes.object,
@@ -1050,16 +1051,17 @@ loop.shared.views = (function(_, mozL10n
             <div className={remoteStreamClasses}>
               <MediaView displayAvatar={!this.props.renderRemoteVideo}
                 isLoading={this.props.isRemoteLoading}
                 mediaType="remote"
                 posterUrl={this.props.remotePosterUrl}
                 srcVideoObject={this.props.remoteSrcVideoObject} />
               { this.state.localMediaAboslutelyPositioned ?
                 this.renderLocalVideo() : null }
+              { this.props.children }
             </div>
             <div className={screenShareStreamClasses}>
               <MediaView displayAvatar={false}
                 isLoading={this.props.isScreenShareLoading}
                 mediaType="screen-share"
                 posterUrl={this.props.screenSharePosterUrl}
                 srcVideoObject={this.props.screenShareVideoObject} />
             </div>
--- a/browser/components/loop/test/desktop-local/index.html
+++ b/browser/components/loop/test/desktop-local/index.html
@@ -90,17 +90,17 @@
     describe("Uncaught Error Check", function() {
       it("should load the tests without errors", function() {
         chai.expect(uncaughtError && uncaughtError.message).to.be.undefined;
       });
     });
 
     describe("Unexpected Warnings Check", function() {
       it("should long only the warnings we expect", function() {
-        chai.expect(caughtWarnings.length).to.eql(27);
+        chai.expect(caughtWarnings.length).to.eql(28);
       });
     });
 
     mocha.run(function () {
       var completeNode = document.createElement("p");
       completeNode.setAttribute("id", "complete");
       completeNode.appendChild(document.createTextNode("Complete"));
       document.getElementById("mocha").appendChild(completeNode);
--- a/browser/components/loop/test/desktop-local/roomViews_test.js
+++ b/browser/components/loop/test/desktop-local/roomViews_test.js
@@ -593,31 +593,16 @@ describe("loop.roomViews", function () {
 
         view = mountTestComponent();
 
         expect(view.getDOMNode().querySelector(".local video")).not.eql(null);
       });
 
     });
 
-    describe("Mute", function() {
-      it("should render local media as audio-only if video is muted",
-        function() {
-          activeRoomStore.setStoreState({
-            roomState: ROOM_STATES.SESSION_CONNECTED,
-            videoMuted: true
-          });
-
-          view = mountTestComponent();
-
-          expect(view.getDOMNode().querySelector(".local-stream-audio"))
-            .not.eql(null);
-        });
-    });
-
     describe("Edit Context", function() {
       it("should show the form when the edit button is clicked", function() {
         view = mountTestComponent();
         var node = view.getDOMNode();
 
         expect(node.querySelector(".room-context")).to.eql(null);
 
         var editButton = node.querySelector(".btn-mute-edit");
--- a/browser/components/loop/test/functional/test_1_browser_call.py
+++ b/browser/components/loop/test/functional/test_1_browser_call.py
@@ -94,17 +94,17 @@ class Test1BrowserCall(MarionetteTestCas
         self.wait_for_element_enabled(button, 120)
 
         button.click()
 
     def local_check_room_self_video(self):
         self.switch_to_chatbox()
 
         # expect a video container on desktop side
-        media_container = self.wait_for_element_displayed(By.CLASS_NAME, "media")
+        media_container = self.wait_for_element_displayed(By.CLASS_NAME, "media-layout")
         self.assertEqual(media_container.tag_name, "div", "expect a video container")
 
         self.check_video(".local-video")
 
     def local_get_and_verify_room_url(self):
         self.switch_to_chatbox()
         button = self.wait_for_element_displayed(By.CLASS_NAME, "btn-copy")
 
--- a/browser/components/loop/ui/ui-showcase.js
+++ b/browser/components/loop/ui/ui-showcase.js
@@ -262,17 +262,20 @@
   });
 
   var endedRoomStore = makeActiveRoomStore({
     roomState: ROOM_STATES.ENDED,
     roomUsed: true
   });
 
   var invitationRoomStore = new loop.store.RoomStore(dispatcher, {
-    mozLoop: navigator.mozLoop
+    mozLoop: navigator.mozLoop,
+    activeRoomStore: makeActiveRoomStore({
+      roomState: ROOM_STATES.INIT
+    })
   });
 
   var roomStore = new loop.store.RoomStore(dispatcher, {
     mozLoop: navigator.mozLoop,
     activeRoomStore: makeActiveRoomStore({
       roomState: ROOM_STATES.HAS_PARTICIPANTS
     })
   });
@@ -281,16 +284,30 @@
     mozLoop: navigator.mozLoop,
     activeRoomStore: makeActiveRoomStore({
       roomState: ROOM_STATES.HAS_PARTICIPANTS,
       mediaConnected: false,
       remoteSrcVideoObject: false
     })
   });
 
+  var desktopRoomStoreMedium = new loop.store.RoomStore(dispatcher, {
+    mozLoop: navigator.mozLoop,
+    activeRoomStore: makeActiveRoomStore({
+      roomState: ROOM_STATES.HAS_PARTICIPANTS
+    })
+  });
+
+  var desktopRoomStoreLarge = new loop.store.RoomStore(dispatcher, {
+    mozLoop: navigator.mozLoop,
+    activeRoomStore: makeActiveRoomStore({
+      roomState: ROOM_STATES.HAS_PARTICIPANTS
+    })
+  });
+
   var desktopLocalFaceMuteActiveRoomStore = makeActiveRoomStore({
     roomState: ROOM_STATES.HAS_PARTICIPANTS,
     videoMuted: true
   });
   var desktopLocalFaceMuteRoomStore = new loop.store.RoomStore(dispatcher, {
     mozLoop: navigator.mozLoop,
     activeRoomStore: desktopLocalFaceMuteActiveRoomStore
   });
@@ -993,86 +1010,133 @@
               React.createElement("div", {className: "standalone"}, 
                 React.createElement(UnsupportedDeviceView, {platform: "ios"})
               )
             )
           ), 
 
           React.createElement(Section, {name: "DesktopRoomConversationView"}, 
             React.createElement(FramedExample, {
-              height: 254, 
+              height: 398, 
+              onContentsRendered: invitationRoomStore.activeRoomStore.forcedUpdate, 
               summary: "Desktop room conversation (invitation, text-chat inclusion/scrollbars don't happen in real client)", 
               width: 298}, 
               React.createElement("div", {className: "fx-embedded"}, 
                 React.createElement(DesktopRoomConversationView, {
                   dispatcher: dispatcher, 
                   localPosterUrl: "sample-img/video-screen-local.png", 
                   mozLoop: navigator.mozLoop, 
                   onCallTerminated: function(){}, 
                   roomState: ROOM_STATES.INIT, 
                   roomStore: invitationRoomStore})
               )
             ), 
 
             React.createElement(FramedExample, {
               dashed: true, 
               height: 394, 
+              onContentsRendered: desktopRoomStoreLoading.activeRoomStore.forcedUpdate, 
               summary: "Desktop room conversation (loading)", 
               width: 298}, 
               /* Hide scrollbars here. Rotating loading div overflows and causes
                scrollbars to appear */
               React.createElement("div", {className: "fx-embedded overflow-hidden"}, 
                 React.createElement(DesktopRoomConversationView, {
                   dispatcher: dispatcher, 
                   localPosterUrl: "sample-img/video-screen-local.png", 
                   mozLoop: navigator.mozLoop, 
                   onCallTerminated: function(){}, 
                   remotePosterUrl: "sample-img/video-screen-remote.png", 
                   roomState: ROOM_STATES.HAS_PARTICIPANTS, 
                   roomStore: desktopRoomStoreLoading})
               )
             ), 
 
-            React.createElement(FramedExample, {height: 254, 
-                           summary: "Desktop room conversation"}, 
+            React.createElement(FramedExample, {
+              dashed: true, 
+              height: 394, 
+              onContentsRendered: roomStore.activeRoomStore.forcedUpdate, 
+              summary: "Desktop room conversation", 
+              width: 298}, 
               React.createElement("div", {className: "fx-embedded"}, 
                 React.createElement(DesktopRoomConversationView, {
                   dispatcher: dispatcher, 
                   localPosterUrl: "sample-img/video-screen-local.png", 
                   mozLoop: navigator.mozLoop, 
                   onCallTerminated: function(){}, 
                   remotePosterUrl: "sample-img/video-screen-remote.png", 
                   roomState: ROOM_STATES.HAS_PARTICIPANTS, 
                   roomStore: roomStore})
               )
             ), 
 
-            React.createElement(FramedExample, {dashed: true, 
-                           height: 394, 
-                           summary: "Desktop room conversation local face-mute", 
-                           width: 298}, 
+            React.createElement(FramedExample, {
+              dashed: true, 
+              height: 482, 
+              onContentsRendered: desktopRoomStoreMedium.activeRoomStore.forcedUpdate, 
+              summary: "Desktop room conversation (medium)", 
+              width: 602}, 
+              React.createElement("div", {className: "fx-embedded"}, 
+                React.createElement(DesktopRoomConversationView, {
+                  dispatcher: dispatcher, 
+                  localPosterUrl: "sample-img/video-screen-local.png", 
+                  mozLoop: navigator.mozLoop, 
+                  onCallTerminated: function(){}, 
+                  remotePosterUrl: "sample-img/video-screen-remote.png", 
+                  roomState: ROOM_STATES.HAS_PARTICIPANTS, 
+                  roomStore: desktopRoomStoreMedium})
+              )
+            ), 
+
+            React.createElement(FramedExample, {
+              dashed: true, 
+              height: 485, 
+              onContentsRendered: desktopRoomStoreLarge.activeRoomStore.forcedUpdate, 
+              summary: "Desktop room conversation (large)", 
+              width: 646}, 
+              React.createElement("div", {className: "fx-embedded"}, 
+                React.createElement(DesktopRoomConversationView, {
+                  dispatcher: dispatcher, 
+                  localPosterUrl: "sample-img/video-screen-local.png", 
+                  mozLoop: navigator.mozLoop, 
+                  onCallTerminated: function(){}, 
+                  remotePosterUrl: "sample-img/video-screen-remote.png", 
+                  roomState: ROOM_STATES.HAS_PARTICIPANTS, 
+                  roomStore: desktopRoomStoreLarge})
+              )
+            ), 
+
+            React.createElement(FramedExample, {
+              dashed: true, 
+              height: 394, 
+              onContentsRendered: desktopLocalFaceMuteRoomStore.activeRoomStore.forcedUpdate, 
+              summary: "Desktop room conversation local face-mute", 
+              width: 298}, 
               React.createElement("div", {className: "fx-embedded"}, 
                 React.createElement(DesktopRoomConversationView, {
                   dispatcher: dispatcher, 
                   mozLoop: navigator.mozLoop, 
                   onCallTerminated: function(){}, 
                   remotePosterUrl: "sample-img/video-screen-remote.png", 
                   roomStore: desktopLocalFaceMuteRoomStore})
               )
             ), 
 
-            React.createElement(FramedExample, {dashed: true, height: 394, 
+            React.createElement(FramedExample, {dashed: true, 
+                           height: 394, 
+                           onContentsRendered: desktopRemoteFaceMuteRoomStore.activeRoomStore.forcedUpdate, 
                            summary: "Desktop room conversation remote face-mute", 
                            width: 298}, 
               React.createElement("div", {className: "fx-embedded"}, 
                 React.createElement(DesktopRoomConversationView, {
                   dispatcher: dispatcher, 
                   localPosterUrl: "sample-img/video-screen-local.png", 
                   mozLoop: navigator.mozLoop, 
                   onCallTerminated: function(){}, 
+                  remotePosterUrl: "sample-img/video-screen-remote.png", 
                   roomStore: desktopRemoteFaceMuteRoomStore})
               )
             )
           ), 
 
           React.createElement(Section, {name: "StandaloneRoomView"}, 
             React.createElement(FramedExample, {cssClass: "standalone", 
                            dashed: true, 
@@ -1379,17 +1443,17 @@
         setTimeout(waitForQueuedFrames, 500);
         return;
       }
       // Put the title back, in case views changed it.
       document.title = "Loop UI Components Showcase";
 
       // This simulates the mocha layout for errors which means we can run
       // this alongside our other unit tests but use the same harness.
-      var expectedWarningsCount = 19;
+      var expectedWarningsCount = 18;
       var warningsMismatch = caughtWarnings.length !== expectedWarningsCount;
       if (uncaughtError || warningsMismatch) {
         $("#results").append("<div class='failures'><em>" +
           ((uncaughtError && warningsMismatch) ? 2 : 1) + "</em></div>");
         if (warningsMismatch) {
           $("#results").append("<li class='test fail'>" +
             "<h2>Unexpected number of warnings detected in UI-Showcase</h2>" +
             "<pre class='error'>Got: " + caughtWarnings.length + "\n" +
--- a/browser/components/loop/ui/ui-showcase.jsx
+++ b/browser/components/loop/ui/ui-showcase.jsx
@@ -262,17 +262,20 @@
   });
 
   var endedRoomStore = makeActiveRoomStore({
     roomState: ROOM_STATES.ENDED,
     roomUsed: true
   });
 
   var invitationRoomStore = new loop.store.RoomStore(dispatcher, {
-    mozLoop: navigator.mozLoop
+    mozLoop: navigator.mozLoop,
+    activeRoomStore: makeActiveRoomStore({
+      roomState: ROOM_STATES.INIT
+    })
   });
 
   var roomStore = new loop.store.RoomStore(dispatcher, {
     mozLoop: navigator.mozLoop,
     activeRoomStore: makeActiveRoomStore({
       roomState: ROOM_STATES.HAS_PARTICIPANTS
     })
   });
@@ -281,16 +284,30 @@
     mozLoop: navigator.mozLoop,
     activeRoomStore: makeActiveRoomStore({
       roomState: ROOM_STATES.HAS_PARTICIPANTS,
       mediaConnected: false,
       remoteSrcVideoObject: false
     })
   });
 
+  var desktopRoomStoreMedium = new loop.store.RoomStore(dispatcher, {
+    mozLoop: navigator.mozLoop,
+    activeRoomStore: makeActiveRoomStore({
+      roomState: ROOM_STATES.HAS_PARTICIPANTS
+    })
+  });
+
+  var desktopRoomStoreLarge = new loop.store.RoomStore(dispatcher, {
+    mozLoop: navigator.mozLoop,
+    activeRoomStore: makeActiveRoomStore({
+      roomState: ROOM_STATES.HAS_PARTICIPANTS
+    })
+  });
+
   var desktopLocalFaceMuteActiveRoomStore = makeActiveRoomStore({
     roomState: ROOM_STATES.HAS_PARTICIPANTS,
     videoMuted: true
   });
   var desktopLocalFaceMuteRoomStore = new loop.store.RoomStore(dispatcher, {
     mozLoop: navigator.mozLoop,
     activeRoomStore: desktopLocalFaceMuteActiveRoomStore
   });
@@ -993,86 +1010,133 @@
               <div className="standalone">
                 <UnsupportedDeviceView platform="ios"/>
               </div>
             </Example>
           </Section>
 
           <Section name="DesktopRoomConversationView">
             <FramedExample
-              height={254}
+              height={398}
+              onContentsRendered={invitationRoomStore.activeRoomStore.forcedUpdate}
               summary="Desktop room conversation (invitation, text-chat inclusion/scrollbars don't happen in real client)"
               width={298}>
               <div className="fx-embedded">
                 <DesktopRoomConversationView
                   dispatcher={dispatcher}
                   localPosterUrl="sample-img/video-screen-local.png"
                   mozLoop={navigator.mozLoop}
                   onCallTerminated={function(){}}
                   roomState={ROOM_STATES.INIT}
                   roomStore={invitationRoomStore} />
               </div>
             </FramedExample>
 
             <FramedExample
               dashed={true}
               height={394}
+              onContentsRendered={desktopRoomStoreLoading.activeRoomStore.forcedUpdate}
               summary="Desktop room conversation (loading)"
               width={298}>
               {/* Hide scrollbars here. Rotating loading div overflows and causes
                scrollbars to appear */}
               <div className="fx-embedded overflow-hidden">
                 <DesktopRoomConversationView
                   dispatcher={dispatcher}
                   localPosterUrl="sample-img/video-screen-local.png"
                   mozLoop={navigator.mozLoop}
                   onCallTerminated={function(){}}
                   remotePosterUrl="sample-img/video-screen-remote.png"
                   roomState={ROOM_STATES.HAS_PARTICIPANTS}
                   roomStore={desktopRoomStoreLoading} />
               </div>
             </FramedExample>
 
-            <FramedExample height={254}
-                           summary="Desktop room conversation">
+            <FramedExample
+              dashed={true}
+              height={394}
+              onContentsRendered={roomStore.activeRoomStore.forcedUpdate}
+              summary="Desktop room conversation"
+              width={298}>
               <div className="fx-embedded">
                 <DesktopRoomConversationView
                   dispatcher={dispatcher}
                   localPosterUrl="sample-img/video-screen-local.png"
                   mozLoop={navigator.mozLoop}
                   onCallTerminated={function(){}}
                   remotePosterUrl="sample-img/video-screen-remote.png"
                   roomState={ROOM_STATES.HAS_PARTICIPANTS}
                   roomStore={roomStore} />
               </div>
             </FramedExample>
 
-            <FramedExample dashed={true}
-                           height={394}
-                           summary="Desktop room conversation local face-mute"
-                           width={298}>
+            <FramedExample
+              dashed={true}
+              height={482}
+              onContentsRendered={desktopRoomStoreMedium.activeRoomStore.forcedUpdate}
+              summary="Desktop room conversation (medium)"
+              width={602}>
+              <div className="fx-embedded">
+                <DesktopRoomConversationView
+                  dispatcher={dispatcher}
+                  localPosterUrl="sample-img/video-screen-local.png"
+                  mozLoop={navigator.mozLoop}
+                  onCallTerminated={function(){}}
+                  remotePosterUrl="sample-img/video-screen-remote.png"
+                  roomState={ROOM_STATES.HAS_PARTICIPANTS}
+                  roomStore={desktopRoomStoreMedium} />
+              </div>
+            </FramedExample>
+
+            <FramedExample
+              dashed={true}
+              height={485}
+              onContentsRendered={desktopRoomStoreLarge.activeRoomStore.forcedUpdate}
+              summary="Desktop room conversation (large)"
+              width={646}>
+              <div className="fx-embedded">
+                <DesktopRoomConversationView
+                  dispatcher={dispatcher}
+                  localPosterUrl="sample-img/video-screen-local.png"
+                  mozLoop={navigator.mozLoop}
+                  onCallTerminated={function(){}}
+                  remotePosterUrl="sample-img/video-screen-remote.png"
+                  roomState={ROOM_STATES.HAS_PARTICIPANTS}
+                  roomStore={desktopRoomStoreLarge} />
+              </div>
+            </FramedExample>
+
+            <FramedExample
+              dashed={true}
+              height={394}
+              onContentsRendered={desktopLocalFaceMuteRoomStore.activeRoomStore.forcedUpdate}
+              summary="Desktop room conversation local face-mute"
+              width={298}>
               <div className="fx-embedded">
                 <DesktopRoomConversationView
                   dispatcher={dispatcher}
                   mozLoop={navigator.mozLoop}
                   onCallTerminated={function(){}}
                   remotePosterUrl="sample-img/video-screen-remote.png"
                   roomStore={desktopLocalFaceMuteRoomStore} />
               </div>
             </FramedExample>
 
-            <FramedExample dashed={true} height={394}
+            <FramedExample dashed={true}
+                           height={394}
+                           onContentsRendered={desktopRemoteFaceMuteRoomStore.activeRoomStore.forcedUpdate}
                            summary="Desktop room conversation remote face-mute"
                            width={298} >
               <div className="fx-embedded">
                 <DesktopRoomConversationView
                   dispatcher={dispatcher}
                   localPosterUrl="sample-img/video-screen-local.png"
                   mozLoop={navigator.mozLoop}
                   onCallTerminated={function(){}}
+                  remotePosterUrl="sample-img/video-screen-remote.png"
                   roomStore={desktopRemoteFaceMuteRoomStore} />
               </div>
             </FramedExample>
           </Section>
 
           <Section name="StandaloneRoomView">
             <FramedExample cssClass="standalone"
                            dashed={true}
@@ -1379,17 +1443,17 @@
         setTimeout(waitForQueuedFrames, 500);
         return;
       }
       // Put the title back, in case views changed it.
       document.title = "Loop UI Components Showcase";
 
       // This simulates the mocha layout for errors which means we can run
       // this alongside our other unit tests but use the same harness.
-      var expectedWarningsCount = 19;
+      var expectedWarningsCount = 18;
       var warningsMismatch = caughtWarnings.length !== expectedWarningsCount;
       if (uncaughtError || warningsMismatch) {
         $("#results").append("<div class='failures'><em>" +
           ((uncaughtError && warningsMismatch) ? 2 : 1) + "</em></div>");
         if (warningsMismatch) {
           $("#results").append("<li class='test fail'>" +
             "<h2>Unexpected number of warnings detected in UI-Showcase</h2>" +
             "<pre class='error'>Got: " + caughtWarnings.length + "\n" +