Bug 1122486 - Upgrade Loop's use of Tokbox SDK 2.2.9.7 to fix issues with calls and rooms intermitently failing to connect. r=nperriault,a=sledru
authorMark Banner <standard8@mozilla.com>
Tue, 27 Jan 2015 11:01:47 +0000
changeset 243067 787bb877e60b
parent 243066 0edd04e1f6d9
child 243068 52b223f4ec70
push id4381
push usermbanner@mozilla.com
push date2015-01-28 14:08 +0000
treeherdermozilla-beta@787bb877e60b [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersnperriault, sledru
bugs1122486
milestone36.0
Bug 1122486 - Upgrade Loop's use of Tokbox SDK 2.2.9.7 to fix issues with calls and rooms intermitently failing to connect. r=nperriault,a=sledru
browser/components/loop/content/js/conversationViews.js
browser/components/loop/content/js/conversationViews.jsx
browser/components/loop/content/js/roomViews.js
browser/components/loop/content/js/roomViews.jsx
browser/components/loop/content/shared/js/views.js
browser/components/loop/content/shared/js/views.jsx
browser/components/loop/content/shared/libs/sdk-content/css/ot.css
browser/components/loop/content/shared/libs/sdk.js
browser/components/loop/standalone/content/js/standaloneRoomViews.js
browser/components/loop/standalone/content/js/standaloneRoomViews.jsx
--- a/browser/components/loop/content/js/conversationViews.js
+++ b/browser/components/loop/content/js/conversationViews.js
@@ -349,17 +349,16 @@ loop.conversationViews = (function(mozL1
       // @see https://bugzilla.mozilla.org/show_bug.cgi?id=1020445
       return {
         insertMode: "append",
         width: "100%",
         height: "100%",
         publishVideo: this.props.video.enabled,
         style: {
           audioLevelDisplayMode: "off",
-          bugDisplayMode: "off",
           buttonDisplayMode: "off",
           nameDisplayMode: "off",
           videoDisabledDisplayMode: "off"
         }
       };
     },
 
     /**
--- a/browser/components/loop/content/js/conversationViews.jsx
+++ b/browser/components/loop/content/js/conversationViews.jsx
@@ -349,17 +349,16 @@ loop.conversationViews = (function(mozL1
       // @see https://bugzilla.mozilla.org/show_bug.cgi?id=1020445
       return {
         insertMode: "append",
         width: "100%",
         height: "100%",
         publishVideo: this.props.video.enabled,
         style: {
           audioLevelDisplayMode: "off",
-          bugDisplayMode: "off",
           buttonDisplayMode: "off",
           nameDisplayMode: "off",
           videoDisabledDisplayMode: "off"
         }
       };
     },
 
     /**
--- a/browser/components/loop/content/js/roomViews.js
+++ b/browser/components/loop/content/js/roomViews.js
@@ -213,17 +213,16 @@ loop.roomViews = (function(mozL10n) {
       // @see https://bugzilla.mozilla.org/show_bug.cgi?id=1020445
       return {
         insertMode: "append",
         width: "100%",
         height: "100%",
         publishVideo: !this.state.videoMuted,
         style: {
           audioLevelDisplayMode: "off",
-          bugDisplayMode: "off",
           buttonDisplayMode: "off",
           nameDisplayMode: "off",
           videoDisabledDisplayMode: "off"
         }
       };
     },
 
     /**
--- a/browser/components/loop/content/js/roomViews.jsx
+++ b/browser/components/loop/content/js/roomViews.jsx
@@ -213,17 +213,16 @@ loop.roomViews = (function(mozL10n) {
       // @see https://bugzilla.mozilla.org/show_bug.cgi?id=1020445
       return {
         insertMode: "append",
         width: "100%",
         height: "100%",
         publishVideo: !this.state.videoMuted,
         style: {
           audioLevelDisplayMode: "off",
-          bugDisplayMode: "off",
           buttonDisplayMode: "off",
           nameDisplayMode: "off",
           videoDisabledDisplayMode: "off"
         }
       };
     },
 
     /**
--- a/browser/components/loop/content/shared/js/views.js
+++ b/browser/components/loop/content/shared/js/views.js
@@ -153,17 +153,16 @@ loop.shared.views = (function(_, OT, l10
     // height set to 100%" to fix video layout on Google Chrome
     // @see https://bugzilla.mozilla.org/show_bug.cgi?id=1020445
     publisherConfig: {
       insertMode: "append",
       width: "100%",
       height: "100%",
       style: {
         audioLevelDisplayMode: "off",
-        bugDisplayMode: "off",
         buttonDisplayMode: "off",
         nameDisplayMode: "off",
         videoDisabledDisplayMode: "off"
       }
     },
 
     getDefaultProps: function() {
       return {
--- a/browser/components/loop/content/shared/js/views.jsx
+++ b/browser/components/loop/content/shared/js/views.jsx
@@ -153,17 +153,16 @@ loop.shared.views = (function(_, OT, l10
     // height set to 100%" to fix video layout on Google Chrome
     // @see https://bugzilla.mozilla.org/show_bug.cgi?id=1020445
     publisherConfig: {
       insertMode: "append",
       width: "100%",
       height: "100%",
       style: {
         audioLevelDisplayMode: "off",
-        bugDisplayMode: "off",
         buttonDisplayMode: "off",
         nameDisplayMode: "off",
         videoDisabledDisplayMode: "off"
       }
     },
 
     getDefaultProps: function() {
       return {
--- a/browser/components/loop/content/shared/libs/sdk-content/css/ot.css
+++ b/browser/components/loop/content/shared/libs/sdk-content/css/ot.css
@@ -113,226 +113,98 @@
     border: 0 none;
 }
 
 
 /* Modal dialog styles */
 
 /* Modal dialog styles */
 
+.OT_dialog-centering {
+  display: table;
+  width: 100%;
+  height: 100%;
+}
+
+.OT_dialog-centering-child {
+  display: table-cell;
+  vertical-align: middle;
+}
+
 .OT_dialog {
-  border: none;
-  border-radius: 0;
-  top: 50%;
-  left: 50%;
-  position: absolute;
-  position: fixed;
-  padding: 0;
+  position: relative;
+
+  box-sizing: border-box;
+  max-width: 576px;
+  margin-right: auto;
+  margin-left: auto;
+  padding: 36px;
+  text-align: center; /* centers all the inline content */
+
   background-color: #363636;
   color: #fff;
-  z-index: 9999;
   box-shadow: 2px 4px 6px #999;
   font-family: 'Didact Gothic', sans-serif;
-}
-
-.OT_dialog-blackout {
-  position: absolute;
-  position: fixed;
-  top: 0;
-  right: 0;
-  bottom: 0;
-  left: 0;
-  background-color: #363636;
-}
-
-.OT_dialog-blackout .OT_dialog {
-  box-shadow: 0 0 0 transparent;
+  font-size: 13px;
+  line-height: 1.4;
 }
 
 .OT_dialog * {
-  font-family: 'Didact Gothic', sans-serif;
-}
-
-.OT_dialog-plugin-prompt {
-  margin-left: -350px;
-  margin-top: -127px;
-  width: 650px;
-  height: 254px;
-}
-
-.OT_dialog-plugin-reinstall {
-  margin-left: -271px;
-  margin-top: -107px;
-  width: 542px;
-  height: 214px;
-}
-
-.OT_dialog-plugin-upgrading {
-  margin-left: -267px;
-  margin-top: -94px;
-  width: 514px;
-  height: 188px;
-}
-
-.OT_dialog-plugin-upgraded {
-  margin-left: -300px;
-  margin-top: -100px;
-  width: 600px;
-  height: 200px;
-}
-
-.OT_dialog-allow-deny-chrome-first {
-  margin-left: -227px;
-  margin-top: -122px;
-  width: 453px;
-  height: 244px;
-}
-
-.OT_dialog-allow-deny-chrome-pre-denied {
-  margin-left: -263px;
-  margin-top: -135px;
-  width: 526px;
-  height: 270px;
-}
-
-.OT_dialog-allow-deny-chrome-now-denied {
-  margin-left: -120px;
-  margin-top: -85px;
-  width: 256px;
-  height: 170px;
-}
-
-.OT_dialog-allow-deny-firefox-denied {
-  margin-left: -160px;
-  margin-top: -105px;
-  width: 320px;
-  height: 190px;
-}
-
-.OT_dialog-allow-deny-firefox-maybe-denied {
-  margin-left: -281px;
-  margin-top: -126px;
-  width: 562px;
-  height: 252px;
-}
-
-.OT_dialog-allow-highlight-chrome {
-  display: inline-block;
-  margin-top: 20px;
-  width: 227px;
-  height: 94px;
-  background-image: url(../images/rtc/access-prompt-chrome.png);
+  font-family: inherit;
+  box-sizing: inherit;
 }
 
 .OT_closeButton {
   color: #999999;
   cursor: pointer;
   font-size: 32px;
-  line-height: 30px;
+  line-height: 36px;
   position: absolute;
-  right: 15px;
+  right: 18px;
   top: 0;
 }
 
 .OT_dialog-messages {
-  position: absolute;
-  top: 32px;
-  left: 32px;
-  right: 32px;
   text-align: center;
 }
 
-.OT_dialog-allow-deny-firefox-maybe-denied .OT_dialog-messages {
-  top: 45px;
-}
-
 
 .OT_dialog-messages-main {
+  margin-bottom: 36px;
+  line-height: 36px;
+
   font-weight: 300;
-  font-size: 18pt;
-  line-height: 24px;
+  font-size: 24px;
 }
 
 .OT_dialog-messages-minor {
-  font-weight: 300;
-  margin-top: 12px;
+  margin-bottom: 18px;
+
   font-size: 13px;
   line-height: 18px;
   color: #A4A4A4;
 }
 
-.OT_dialog-allow-deny-firefox-maybe-denied .OT_dialog-messages-minor {
-  margin-top: 4px;
-}
-
 .OT_dialog-messages-minor strong {
-  font-weight: 300;
   color: #ffffff;
 }
 
-.OT_dialog-hidden {
-  display: none;
-}
-
-.OT_dialog-single-button {
-  position: absolute;
-  bottom: 41px;
-  left: 50%;
-  margin-left: -97px;
-  height: 47px;
-  width: 193px;
-}
-
-
-.OT_dialog-single-button-wide {
-    bottom: 35px;
-    height: 140px;
-    left: 5px;
-    position: absolute;
-    right: 0;
-}
-  .OT_dialog-single-button-with-title {
-      margin: 0 auto;
-      padding-left: 30px;
-      padding-right: 30px;
-      width: 270px;
-  }
-
-
-.OT_dialog-button-pair {
-  position: absolute;
-  bottom: 45px;
-  left: 5px;
-  right: 0;
-  height: 94px;
-}
-
-.OT_dialog-button-with-title {
-  padding-left: 30px;
-  padding-right: 30px;
-  width: 260px;
-  float: left;
-}
-
-.OT_dialog-button-pair-seperator {
-  border-right: 1px solid #555555;
-  height: 112px;
-  width: 1px;
-  float: left;
+.OT_dialog-actions-card {
+  display: inline-block;
 }
 
 .OT_dialog-button-title {
+  margin-bottom: 18px;
+  line-height: 18px;
+
   font-weight: 300;
   text-align: center;
-  margin-bottom: 15px;
   font-size: 14px;
-  line-height: 150%;
   color: #999999;
 }
-
 .OT_dialog-button-title label {
   color: #999999;
 }
 
 .OT_dialog-button-title a,
 .OT_dialog-button-title a:link,
 .OT_dialog-button-title a:active {
   color: #02A1DE;
@@ -340,167 +212,96 @@
 
 .OT_dialog-button-title strong {
   color: #ffffff;
   font-weight: 100;
   display: block;
 }
 
 .OT_dialog-button {
-  font-weight: 100;
-  display: block;
-  line-height: 50px;
-  height: 47px;
+  display: inline-block;
+
+  margin-bottom: 18px;
+  padding: 0 1em;
+
   background-color: #1CA3DC;
   text-align: center;
-  font-size: 16pt;
   cursor: pointer;
 }
 
 .OT_dialog-button.OT_dialog-button-disabled {
   cursor: not-allowed;
 
   /* IE 8 */
   -ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=50)";
 
   opacity: 0.5;
 }
 
-.OT_dialog-button.OT_dialog-button-large {
-  line-height: 60px;
-  height: 58px;
+.OT_dialog-button-large {
+  line-height: 36px;
+  padding-top: 9px;
+  padding-bottom: 9px;
+
+  font-weight: 100;
+  font-size: 24px;
 }
 
-.OT_dialog-button.OT_dialog-button-small {
+.OT_dialog-button-small {
+  line-height: 18px;
+  padding-top: 9px;
+  padding-bottom: 9px;
+
   background-color: #444444;
   color: #999999;
-  font-size: 12pt;
-  height: 40px;
-  line-height: 40px;
-  margin: 20px auto 0 auto;
-  width: 86px;
+  font-size: 16px;
 }
 
 .OT_dialog-progress-bar {
+  display: inline-block; /* prevents margin collapse */
+  width: 100%;
+  margin-top: 5px;
+  margin-bottom: 41px;
+
   border: 1px solid #4E4E4E;
   height: 8px;
 }
 
 .OT_dialog-progress-bar-fill {
+  height: 100%;
+
   background-color: #29A4DA;
-  height: 10px;
-  margin-top: -1px;
-  margin-left: -1px;
-  margin-right: -1px;
 }
 
 .OT_dialog-plugin-upgrading .OT_dialog-plugin-upgrade-percentage {
-  font-size: 36pt;
+  line-height: 54px;
+
+  font-size: 48px;
   font-weight: 100;
 }
 
-.OT_dialog-plugin-upgrading .OT_dialog-progress-bar {
-  margin-top: 25px;
-  margin-bottom: 25px;
-}
-
-.OT_dialog-3steps {
-  margin-top: 24px;
-}
-
-.OT_dialog-allow-deny-firefox-maybe-denied .OT_dialog-3steps {
-  margin-top: 21px;
-}
-
-.OT_dialog-3steps-step {
-  float: left;
-  -moz-box-sizing: border-box;
-       box-sizing: border-box;
-  width: 33%;
-  height: 140px;
-  padding: 0 10px;
-  color: #A4A4A4;
-}
+/* Helpers */
 
-.OT_dialog-3steps-seperator {
-  float: left;
-  -moz-box-sizing: border-box;
-       box-sizing: border-box;
-  margin-top: 10px;
-  width: 1px;
-  height: 68px;
-  background-color: #555555;
-}
-
-.OT_dialog-allow-deny-chrome-pre-denied .OT_dialog-3steps-seperator {
-  margin-top: 16px;
-}
-
-.OT_dialog-3steps-step-num {
-  font-size: 20px;
-  background-color: #2AA3D8;
-  border-radius: 20px;
-  line-height: 33px;
-  height: 33px;
-  width: 33px;
-  margin: 0 auto 17px;
+.OT_centered {
+  position: fixed;
+  left: 50%;
+  top: 50%;
+  margin: 0;
 }
 
-.OT_dialog-allow-deny-chrome-pre-denied .OT_dialog-3steps-step-num {
-  margin-bottom: 10px;
-}
-
-.OT_dialog-allow-camera-icon {
-  background-color: #000;
-  width: 113px;
-  height: 48px;
-  margin: 10px auto 0;
-  background-image: url(../images/rtc/access-predenied-chrome.png);
-}
-
-/* Publisher Deny Helpers */
-
-.OT_publisher-denied-firefox {
-  background-color: #fff;
+.OT_dialog-hidden {
+  display: none;
 }
 
-.OT_publisher-denied-firefox p {
-  width: 232px;
-  height: 103px;
-  top: 50%;
-  left: 50%;
+.OT_dialog-button-block {
   display: block;
-  position: absolute;
-  margin-top: -52px;
-  margin-left: -116px;
-  background-image: url(../images/rtc/access-denied-firefox.png);
-  background-position: 50% 0;
-  background-repeat: no-repeat;
 }
 
-.OT_publisher-denied-firefox span {
-  display: block;
-  position: absolute;
-  bottom: 0;
-  right: 0;
-  left: 0;
-  margin: 0 auto;
-  width: 232px;
-  height: 31px;
-  background-image: url(../images/rtc/access-denied-copy-firefox.png);
-  text-indent: 100%;
-  white-space: nowrap;
-  overflow: hidden;
-}
-
-.OT_centered {
-	position: fixed;
-	left: 50%;
-	top: 50%;
-	margin: 0;
+.OT_dialog-no-natural-margin {
+  margin-bottom: 0;
 }
 
 /* Publisher and Subscriber styles */
 
 .OT_publisher, .OT_subscriber {
     position: relative;
     min-width: 48px;
     min-height: 48px;
@@ -798,56 +599,34 @@
 .OT_mini .OT_name.OT_mode-on,
 .OT_mini .OT_name.OT_mode-auto,
 .OT_mini:hover .OT_name.OT_mode-auto {
     display: none;
 }
 
 .OT_publisher .OT_name,
 .OT_subscriber .OT_name {
-    left: 24px;
+    left: 10px;
     right: 37px;
     height: 34px;
-}
-
-.OT_publisher .OT_name-no-bug,
-.OT_subscriber .OT_name-no-bug {
-    left: 10px;
     padding-left: 0;
 }
 
 .OT_publisher .OT_mute,
-.OT_subscriber .OT_mute,
-.OT_publisher .OT_opentok,
-.OT_subscriber .OT_opentok {
+.OT_subscriber .OT_mute {
     border: none;
     cursor: pointer;
     display: block;
     position: absolute;
     text-align: center;
     text-indent: -9999em;
     background-color: transparent;
     background-repeat: no-repeat;
 }
 
-.OT_publisher .OT_opentok,
-.OT_subscriber .OT_opentok {
-    background: url(../images/rtc/buttons.png) 0 -32px no-repeat;
-    cursor: default;
-    height: 18px;
-    left: 8px;
-    line-height: 18px;
-    top: 8px;
-    width: 16px;
-}
-
-.OT_micro .OT_opentok {
-    display: none !important;
-}
-
 .OT_publisher .OT_mute,
 .OT_subscriber .OT_mute {
     right: 0;
     top: 0;
     border-left: 1px solid rgba(255, 255, 255, 0.2);
     height: 36px;
     width: 37px;
 }
@@ -878,31 +657,16 @@
     background-position: 8px 7px;
 }
 
 .OT_subscriber .OT_mute.OT_active {
     background-image: url(../images/rtc/speaker-off.png);
     background-position: 7px 7px;
 }
 
-/* Disabling this for now - see https://jira.tokbox.com/browse/OPENTOK-8870
-.OT_publisher .OT_opentok:hover:after,
-.OT_subscriber .OT_opentok:hover:after {
-    content: 'tokbox';
-    color: #fff;
-    font-weight: bold;
-    font-size: 14px;
-    letter-spacing: -1px;
-    top: 20px;
-    opacity: 0.5;
-    position: absolute;
-    text-indent: 0;
-    top: 0;
-}*/
-
 /**
  * Styles for display modes
  *
  * Note: It's important that these completely control the display and opacity
  * attributes, no other selectors should atempt to change them.
  */
 
 /* Default display mode transitions for various chrome elements */
@@ -970,33 +734,16 @@
 .OT_subscriber .OT_edge-bar-item.OT_edge-bottom.OT_mode-on,
 .OT_publisher:hover .OT_edge-bar-item.OT_edge-bottom.OT_mode-auto,
 .OT_subscriber:hover .OT_edge-bar-item.OT_edge-bottom.OT_mode-auto {
     top: auto;
     bottom: 0;
     opacity: 1;
 }
 
-.OT_publisher .OT_opentok.OT_mode-off,
-.OT_publisher .OT_opentok.OT_mode-auto,
-.OT_subscriber .OT_opentok.OT_mode-off,
-.OT_subscriber .OT_opentok.OT_mode-auto  {
-    top: -17px;
-}
-
-.OT_publisher .OT_opentok.OT_mode-on,
-.OT_publisher .OT_opentok.OT_mode-auto.OT_mode-on-hold,
-.OT_publisher:hover .OT_opentok.OT_mode-auto,
-.OT_subscriber .OT_opentok.OT_mode-on,
-.OT_subscriber .OT_opentok.OT_mode-auto.OT_mode-on-hold,
-.OT_subscriber:hover .OT_opentok.OT_mode-auto {
-    top: 8px;
-}
-
-
 /* Contains the video element, used to fix video letter-boxing */
 .OT_video-container {
     position: absolute;
     background-color: #000000;
     overflow: hidden;
 }
 
 .OT_hidden-audio {
@@ -1090,17 +837,17 @@
 }
 
 .OT_audio-level-meter__value {
     position: absolute;
     border-radius: 50%;
     background-image: radial-gradient(circle, rgba(151,206,0,1) 0%, rgba(151,206,0,0) 100%);
 }
 
-.OT_audio-level-meter {
+.OT_audio-level-meter.OT_mode-off {
     display: none;
 }
 
 .OT_audio-level-meter.OT_mode-on,
 .OT_audio-only .OT_audio-level-meter.OT_mode-auto {
     display: block;
 }
 
--- a/browser/components/loop/content/shared/libs/sdk.js
+++ b/browser/components/loop/content/shared/libs/sdk.js
@@ -1,25 +1,25 @@
 /**
- * @license  OpenTok JavaScript Library v2.2.9.1
+ * @license  OpenTok JavaScript Library v2.2.9.7
  * http://www.tokbox.com/
  *
  * Copyright (c) 2014 TokBox, Inc.
  * Released under the MIT license
  * http://opensource.org/licenses/MIT
  *
- * Date: September 08 10:17:05 2014
+ * Date: January 26 03:18:02 2015
  */
 
 (function(window) {
   if (!window.OT) window.OT = {};
 
   OT.properties = {
-    version: 'v2.2.9.1',         // The current version (eg. v2.0.4) (This is replaced by gradle)
-    build: '72b534e',    // The current build hash (This is replaced by gradle)
+    version: 'v2.2.9.7',         // The current version (eg. v2.0.4) (This is replaced by gradle)
+    build: '59e99bc',    // The current build hash (This is replaced by gradle)
 
     // Whether or not to turn on debug logging by default
     debug: 'false',
     // The URL of the tokbox website
     websiteURL: 'http://www.tokbox.com',
 
     // The URL of the CDN
     cdnURL: 'http://static.opentok.com',
@@ -3098,16 +3098,24 @@
   * @memberof OT
   * @function
   * @see <a href="#setLogLevel">OT.setLogLevel()</a>
   */
 
 })(window);
 !(function() {
 
+  var adjustModal = function(callback) {
+    return function setFullHeightDocument(window, document) {
+      // required in IE8
+      document.querySelector('html').style.height = document.body.style.height = '100%';
+      callback(window, document);
+    };
+  };
+
   var addCss = function(document, url, callback) {
     var head = document.head || document.getElementsByTagName('head')[0];
     var cssTag = OT.$.createElement('link', {
       type: 'text/css',
       media: 'screen',
       rel: 'stylesheet',
       href: url
     });
@@ -3162,213 +3170,20 @@
   var linkElement = function(children, href, classes) {
     var link = templateElement.call(this, classes || '', children, 'a');
     link.setAttribute('href', href);
     return link;
   };
 
   OT.Dialogs = {};
 
-  OT.Dialogs.AllowDeny = {
-    Chrome: {},
-    Firefox: {}
-  };
-
-  OT.Dialogs.AllowDeny.Chrome.initialPrompt = function() {
-    var modal = new OT.$.Modal(function(window, document) {
-
-      var el = OT.$.bind(templateElement, document),
-          close, root;
-
-      close = el('OT_closeButton', '&times;')
-        .on('click', function() {
-          modal.trigger('closeButtonClicked');
-          modal.close();
-        });
-
-      root = el('OT_root OT_dialog OT_dialog-allow-deny-chrome-first', [
-        close,
-        el('OT_dialog-messages', [
-          el('OT_dialog-messages-main', 'Allow camera and mic access'),
-          el('OT_dialog-messages-minor', 'Click the Allow button in the upper-right corner ' +
-            'of your browser to enable real-time communication.'),
-          el('OT_dialog-allow-highlight-chrome')
-        ])
-      ]);
-
-      addDialogCSS(document, [], function() {
-        document.body.appendChild(root);
-      });
-
-    });
-    return modal;
-  };
-
-  OT.Dialogs.AllowDeny.Chrome.previouslyDenied = function(website) {
-    var modal = new OT.$.Modal(function(window, document) {
-
-      var el = OT.$.bind(templateElement, document),
-          close,
-          root;
-
-      close = el('OT_closeButton', '&times;')
-        .on('click', function() {
-          modal.trigger('closeButtonClicked');
-          modal.close();
-        });
-
-      root = el('OT_root OT_dialog OT_dialog-allow-deny-chrome-pre-denied', [
-        close,
-        el('OT_dialog-messages', [
-          el('OT_dialog-messages-main', 'Allow camera and mic access'),
-          el('OT_dialog-messages-minor', [
-            'To interact with this app, follow these 3 steps:',
-            el('OT_dialog-3steps', [
-              el('OT_dialog-3steps-step', [
-                el('OT_dialog-3steps-step-num', '1'),
-                'Find this icon in the URL bar and click it',
-                el('OT_dialog-allow-camera-icon')
-              ]),
-              el('OT_dialog-3steps-seperator'),
-              el('OT_dialog-3steps-step', [
-                el('OT_dialog-3steps-step-num', '2'),
-                'Select "Ask if ' + website + ' wants to access your camera and mic" ' +
-                  'and then click Done.'
-              ]),
-              el('OT_dialog-3steps-seperator'),
-              el('OT_dialog-3steps-step', [
-                el('OT_dialog-3steps-step-num', '3'),
-                'Refresh your browser.'
-              ])
-            ])
-          ])
-        ])
-      ]);
-
-      addDialogCSS(document, [], function() {
-        document.body.appendChild(root);
-      });
-
-    });
-    return modal;
-  };
-
-  OT.Dialogs.AllowDeny.Chrome.deniedNow = function() {
-    var modal = new OT.$.Modal(function(window, document) {
-
-      var el = OT.$.bind(templateElement, document),
-          root;
-
-      root = el('OT_root OT_dialog-blackout',
-        el('OT_dialog OT_dialog-allow-deny-chrome-now-denied', [
-          el('OT_dialog-messages', [
-            el('OT_dialog-messages-main ',
-              el('OT_dialog-allow-camera-icon')
-            ),
-            el('OT_dialog-messages-minor',
-              'Find & click this icon to allow camera and mic access.'
-            )
-          ])
-        ])
-      );
-
-      addDialogCSS(document, [], function() {
-        document.body.appendChild(root);
-      });
-
-    });
-    return modal;
-  };
-
-  OT.Dialogs.AllowDeny.Firefox.maybeDenied = function() {
-    var modal = new OT.$.Modal(function(window, document) {
-
-      var el = OT.$.bind(templateElement, document),
-          close,
-          root;
-
-      close = el('OT_closeButton', '&times;')
-        .on('click', function() {
-          modal.trigger('closeButtonClicked');
-          modal.close();
-        });
-
-      root = el('OT_root OT_dialog OT_dialog-allow-deny-firefox-maybe-denied', [
-        close,
-        el('OT_dialog-messages', [
-          el('OT_dialog-messages-main', 'Please allow camera & mic access'),
-          el('OT_dialog-messages-minor', [
-            'To interact with this app, follow these 3 steps:',
-            el('OT_dialog-3steps', [
-              el('OT_dialog-3steps-step', [
-                el('OT_dialog-3steps-step-num', '1'),
-                'Reload the page, or click the camera icon ' +
-                  'in the browser URL bar.'
-              ]),
-              el('OT_dialog-3steps-seperator'),
-              el('OT_dialog-3steps-step', [
-                el('OT_dialog-3steps-step-num', '2'),
-                'In the menu, select your camera & mic.'
-              ]),
-              el('OT_dialog-3steps-seperator'),
-              el('OT_dialog-3steps-step', [
-                el('OT_dialog-3steps-step-num', '3'),
-                'Click "Share Selected Devices."'
-              ])
-            ])
-          ])
-        ])
-      ]);
-
-      addDialogCSS(document, [], function() {
-        document.body.appendChild(root);
-      });
-
-    });
-    return modal;
-  };
-
-  OT.Dialogs.AllowDeny.Firefox.denied = function() {
-    var modal = new OT.$.Modal(function(window, document) {
-
-      var el = OT.$.bind(templateElement, document),
-          btn = OT.$.bind(templateElement, document, 'OT_dialog-button OT_dialog-button-large'),
-          root,
-          refreshButton;
-
-      refreshButton = btn('Reload')
-        .on('click', function() {
-          modal.trigger('refresh');
-        });
-
-      root = el('OT_root OT_dialog-blackout',
-        el('OT_dialog OT_dialog-allow-deny-firefox-denied', [
-          el('OT_dialog-messages', [
-            el('OT_dialog-messages-minor',
-              'Access to camera and microphone has been denied. ' +
-              'Click the button to reload page.'
-            )
-          ]),
-          el('OT_dialog-single-button', refreshButton)
-        ])
-      );
-
-      addDialogCSS(document, [], function() {
-        document.body.appendChild(root);
-      });
-
-    });
-
-    return modal;
-  };
-
   OT.Dialogs.Plugin = {};
 
   OT.Dialogs.Plugin.promptToInstall = function() {
-    var modal = new OT.$.Modal(function(window, document) {
+    var modal = new OT.$.Modal(adjustModal(function(window, document) {
 
       var el = OT.$.bind(templateElement, document),
           btn = function(children, size) {
             var classes = 'OT_dialog-button ' +
                           (size ? 'OT_dialog-button-' + size : 'OT_dialog-button-large'),
                 b = el(classes, children);
 
             b.enable = function() {
@@ -3386,22 +3201,25 @@
           downloadButton = btn('Download plugin'),
           cancelButton = btn('cancel', 'small'),
           refreshButton = btn('Refresh browser'),
           acceptEULA,
           checkbox,
           close,
           root;
 
+      OT.$.addClass(cancelButton, 'OT_dialog-no-natural-margin OT_dialog-button-block');
+      OT.$.addClass(refreshButton, 'OT_dialog-no-natural-margin');
+
       function onDownload() {
         modal.trigger('download');
         setTimeout(function() {
           root.querySelector('.OT_dialog-messages-main').innerHTML =
                                               'Plugin installation successful';
-          var sections = root.querySelectorAll('.OT_dialog-single-button-wide');
+          var sections = root.querySelectorAll('.OT_dialog-section');
           OT.$.addClass(sections[0], 'OT_dialog-hidden');
           OT.$.removeClass(sections[1], 'OT_dialog-hidden');
         }, 3000);
       }
 
       function onRefresh() {
         modal.trigger('refresh');
       }
@@ -3446,127 +3264,143 @@
         });
 
       acceptEULA = linkElement.call(document,
                                     'end-user license agreement',
                                     'http://tokbox.com/support/ie-eula');
 
       checkbox = checkBoxElement.call(document, null, 'acceptEULA', onToggleEULA);
 
-      root = el('OT_root OT_dialog OT_dialog-plugin-prompt', [
-        close,
-        el('OT_dialog-messages', [
-          el('OT_dialog-messages-main', 'This app requires real-time communication')
-        ]),
-        el('OT_dialog-single-button-wide', [
-          el('OT_dialog-single-button-with-title', [
-            el('OT_dialog-button-title', [
-              checkbox,
-              (function() {
-                var x = el('', 'accept', 'label');
-                x.setAttribute('for', checkbox.id);
-                x.style.margin = '0 5px';
-                return x;
-              })(),
-              acceptEULA
+      root = el('OT_dialog-centering', [
+        el('OT_dialog-centering-child', [
+          el('OT_root OT_dialog OT_dialog-plugin-prompt', [
+            close,
+            el('OT_dialog-messages', [
+              el('OT_dialog-messages-main', 'This app requires real-time communication')
             ]),
-            downloadButton,
-            cancelButton
-          ])
-        ]),
-        el('OT_dialog-single-button-wide OT_dialog-hidden', [
-          el('OT_dialog-single-button-with-title', [
-            el('OT_dialog-button-title', [
-              'You can now enjoy webRTC enabled video via Internet Explorer.'
+            el('OT_dialog-section', [
+              el('OT_dialog-single-button-with-title', [
+                el('OT_dialog-button-title', [
+                  checkbox,
+                  (function() {
+                    var x = el('', 'accept', 'label');
+                    x.setAttribute('for', checkbox.id);
+                    x.style.margin = '0 5px';
+                    return x;
+                  })(),
+                  acceptEULA
+                ]),
+                el('OT_dialog-actions-card', [
+                  downloadButton,
+                  cancelButton
+                ])
+              ])
             ]),
-            refreshButton
+            el('OT_dialog-section OT_dialog-hidden', [
+              el('OT_dialog-button-title', [
+                'You can now enjoy webRTC enabled video via Internet Explorer.'
+              ]),
+              refreshButton
+            ])
           ])
         ])
       ]);
 
       addDialogCSS(document, [], function() {
         document.body.appendChild(root);
       });
 
-    });
+    }));
     return modal;
   };
 
   OT.Dialogs.Plugin.promptToReinstall = function() {
-    var modal = new OT.$.Modal(function(window, document) {
+    var modal = new OT.$.Modal(adjustModal(function(window, document) {
 
       var el = OT.$.bind(templateElement, document),
           close,
           okayButton,
           root;
 
       close = el('OT_closeButton', '&times;');
-      okayButton = el('OT_dialog-button', 'Okay');
+      okayButton =
+        el('OT_dialog-button OT_dialog-button-large OT_dialog-no-natural-margin', 'Okay');
 
       OT.$.on(okayButton, 'click', function() {
         modal.trigger('okay');
       });
 
       OT.$.on(close, 'click', function() {
         modal.trigger('closeButtonClicked');
         modal.close();
       });
 
-      root = el('OT_ROOT OT_dialog OT_dialog-plugin-reinstall', [
-        close,
-        el('OT_dialog-messages', [
-          el('OT_dialog-messages-main', 'Reinstall Opentok Plugin'),
-          el('OT_dialog-messages-minor', 'Uh oh! Try reinstalling the OpenTok plugin again to ' +
-            'enable real-time video communication for Internet Explorer.')
-        ]),
-        el('OT_dialog-single-button', okayButton)
+      root = el('OT_dialog-centering', [
+        el('OT_dialog-centering-child', [
+          el('OT_ROOT OT_dialog OT_dialog-plugin-reinstall', [
+            close,
+            el('OT_dialog-messages', [
+              el('OT_dialog-messages-main', 'Reinstall Opentok Plugin'),
+              el('OT_dialog-messages-minor', 'Uh oh! Try reinstalling the OpenTok plugin ' +
+                'again to enable real-time video communication for Internet Explorer.')
+            ]),
+            el('OT_dialog-section', [
+              el('OT_dialog-single-button', okayButton)
+            ])
+          ])
+        ])
       ]);
 
       addDialogCSS(document, [], function() {
         document.body.appendChild(root);
       });
 
-    });
+    }));
 
     return modal;
   };
 
   OT.Dialogs.Plugin.updateInProgress = function() {
 
     var progressBar,
         progressText,
         progressValue = 0;
 
-    var modal = new OT.$.Modal(function(window, document) {
+    var modal = new OT.$.Modal(adjustModal(function(window, document) {
 
       var el = OT.$.bind(templateElement, document),
           root;
 
       progressText = el('OT_dialog-plugin-upgrade-percentage', '0%', 'strong');
 
       progressBar = el('OT_dialog-progress-bar-fill');
 
-      root = el('OT_ROOT OT_dialog OT_dialog-plugin-upgrading', [
-        el('OT_dialog-messages', [
-          el('OT_dialog-messages-main', [
-            'One moment please... ',
-            progressText
-          ]),
-          el('OT_dialog-progress-bar', progressBar),
-          el('OT_dialog-messages-minor', 'Please wait while the OpenTok plugin is updated')
+      root = el('OT_dialog-centering', [
+        el('OT_dialog-centering-child', [
+          el('OT_ROOT OT_dialog OT_dialog-plugin-upgrading', [
+            el('OT_dialog-messages', [
+              el('OT_dialog-messages-main', [
+                'One moment please... ',
+                progressText
+              ]),
+              el('OT_dialog-progress-bar', progressBar),
+              el('OT_dialog-messages-minor OT_dialog-no-natural-margin',
+                'Please wait while the OpenTok plugin is updated')
+            ])
+          ])
         ])
       ]);
 
       addDialogCSS(document, [], function() {
         document.body.appendChild(root);
         if(progressValue != null) {
           modal.setUpdateProgress(progressValue);
         }
       });
-    });
+    }));
 
     modal.setUpdateProgress = function(newProgress) {
       if(progressBar && progressText) {
         if(newProgress > 99) {
           OT.$.css(progressBar, 'width', '');
           progressText.innerHTML = '100%';
         } else if(newProgress < 1) {
           OT.$.css(progressBar, 'width', '0%');
@@ -3579,48 +3413,54 @@
         progressValue = newProgress;
       }
     };
 
     return modal;
   };
 
   OT.Dialogs.Plugin.updateComplete = function(error) {
-    var modal = new OT.$.Modal(function(window, document) {
+    var modal = new OT.$.Modal(adjustModal(function(window, document) {
       var el = OT.$.bind(templateElement, document),
           reloadButton,
           root;
 
-      reloadButton = el('OT_dialog-button', 'Reload').on('click', function() {
-        modal.trigger('reload');
-      });
+      reloadButton =
+        el('OT_dialog-button OT_dialog-button-large OT_dialog-no-natural-margin', 'Reload')
+          .on('click', function() {
+            modal.trigger('reload');
+          });
 
       var msgs;
 
       if(error) {
         msgs = ['Update Failed.', error + '' || 'NO ERROR'];
       } else {
         msgs = ['Update Complete.',
           'The OpenTok plugin has been succesfully updated. ' +
           'Please reload your browser.'];
       }
 
-      root = el('OT_root OT_dialog OT_dialog-plugin-upgraded', [
-        el('OT_dialog-messages', [
-          el('OT_dialog-messages-main', msgs[0]),
-          el('OT_dialog-messages-minor', msgs[1])
-        ]),
-        el('OT_dialog-single-button', reloadButton)
+      root = el('OT_dialog-centering', [
+        el('OT_dialog-centering-child', [
+          el('OT_root OT_dialog OT_dialog-plugin-upgraded', [
+            el('OT_dialog-messages', [
+              el('OT_dialog-messages-main', msgs[0]),
+              el('OT_dialog-messages-minor', msgs[1])
+            ]),
+            el('OT_dialog-single-button', reloadButton)
+          ])
+        ])
       ]);
 
       addDialogCSS(document, [], function() {
         document.body.appendChild(root);
       });
 
-    });
+    }));
 
     return modal;
 
   };
 
 
 })();
 !(function(window) {
@@ -3806,24 +3646,22 @@
 
     OT.$.eventing(_this);
 
     return _this;
   })();
 
 })(window);
 /**
- * @license  TB Plugin 0.4.0.8 72b534e HEAD
+ * @license  TB Plugin 0.4.0.8 59e99bc HEAD
  * http://www.tokbox.com/
  *
- * Copyright (c) 2014 TokBox, Inc.
- * Released under the MIT license
- * http://opensource.org/licenses/MIT
- *
- * Date: September 08 10:17:49 2014
+ * Copyright (c) 2015 TokBox, Inc.
+ *
+ * Date: January 26 03:18:16 2015
  *
  */
 
 /* jshint globalstrict: true, strict: false, undef: true, unused: false,
           trailing: true, browser: true, smarttabs:true */
 /* global scope:true, OT:true */
 /* exported TBPlugin */
 
@@ -3950,16 +3788,82 @@ var shim = function shim () {
 
       return res;
     };
   }
 };
 // tb_require('./header.js')
 // tb_require('./shims.js')
 
+/* global OT:true */
+/* exported PluginRumorSocket */
+
+var PluginRumorSocket = function(plugin, server) {
+  var connected = false,
+      rumorID;
+
+  try {
+    rumorID = plugin._.RumorInit(server, '');
+  }
+  catch(e) {
+    OT.error('Error creating the Rumor Socket: ', e.message);
+  }
+
+  if(!rumorID) {
+    throw new Error('Could not initialise plugin rumor connection');
+  }
+
+  var socket = {
+    open: function() {
+      connected = true;
+      plugin._.RumorOpen(rumorID);
+    },
+
+    close: function(code, reason) {
+      if (!connected) return;
+      connected = false;
+
+      plugin._.RumorClose(rumorID, code, reason);
+      plugin.removeRef(this);
+    },
+
+    destroy: function() {
+      this.close();
+    },
+
+    send: function(msg) {
+      plugin._.RumorSend(rumorID, msg.type, msg.toAddress,
+        JSON.parse(JSON.stringify(msg.headers)), msg.data);
+    },
+
+    onOpen: function(callback) {
+      plugin._.SetOnRumorOpen(rumorID, callback);
+    },
+
+    onClose: function(callback) {
+      plugin._.SetOnRumorClose(rumorID, callback);
+    },
+
+    onError: function(callback) {
+      plugin._.SetOnRumorError(rumorID, callback);
+    },
+
+    onMessage: function(callback) {
+      plugin._.SetOnRumorMessage(rumorID, callback);
+    }
+  };
+
+  plugin.addRef(socket);
+  return socket;
+
+};
+
+// tb_require('./header.js')
+// tb_require('./shims.js')
+
 /* jshint globalstrict: true, strict: false, undef: true, unused: true,
           trailing: true, browser: true, smarttabs:true */
 /* global OT:true, TBPlugin:true, pluginInfo:true, debug:true, scope:true,
           _document:true */
 /* exported createMediaCaptureController:true, createPeerController:true,
             injectObject:true, plugins:true, mediaCaptureObject:true,
             removeAllObjects:true, curryCallAsync:true */
 
@@ -4351,470 +4255,16 @@ var VideoContainer = function VideoConta
 };
 
 // tb_require('./header.js')
 // tb_require('./shims.js')
 // tb_require('./plugin_object.js')
 
 /* jshint globalstrict: true, strict: false, undef: true, unused: true,
           trailing: true, browser: true, smarttabs:true */
-/* global OT:true, TBPlugin:true, pluginInfo:true, ActiveXObject:true,
-          injectObject:true, curryCallAsync:true */
-
-/* exported AutoUpdater:true */
-var AutoUpdater;
-
-(function() {
-
-  var autoUpdaterController,
-      updaterMimeType,        // <- cached version, use getInstallerMimeType instead
-      installedVersion = -1;  // <- cached version, use getInstallerMimeType instead
-
-
-  var versionGreaterThan = function versionGreaterThan (version1,version2) {
-    if (version1 === version2) return false;
-
-    var v1 = version1.split('.'),
-        v2 = version2.split('.');
-
-    v1 = parseFloat(parseInt(v1.shift(), 10) + '.' +
-                      v1.map(function(vcomp) { return parseInt(vcomp, 10); }).join(''));
-
-    v2 = parseFloat(parseInt(v2.shift(), 10) + '.' +
-                      v2.map(function(vcomp) { return parseInt(vcomp, 10); }).join(''));
-
-
-    return v1 > v2;
-  };
-
-
-  // Work out the full mimeType (including the currently installed version)
-  // of the installer.
-  var findMimeTypeAndVersion = function findMimeTypeAndVersion () {
-
-    if (updaterMimeType !== void 0) {
-      return updaterMimeType;
-    }
-
-    var activeXControlId = 'TokBox.otiePluginInstaller',
-        unversionedMimeType = 'application/x-otieplugininstaller',
-        plugin = navigator.plugins[activeXControlId];
-
-    installedVersion = -1;
-
-
-    if (plugin) {
-      // Look through the supported mime-types for the version
-      // There should only be one mime-type in our use case, and
-      // if there's more than one they should all have the same
-      // version.
-      var numMimeTypes = plugin.length,
-          extractVersion = new RegExp(unversionedMimeType.replace('-', '\\-') +
-                                                            ',version=([0-9]+)', 'i'),
-          mimeType,
-          bits;
-
-      for (var i=0; i<numMimeTypes; ++i) {
-        mimeType = plugin[i];
-
-        // Look through the supported mimeTypes and find
-        // the newest one.
-        if (mimeType && mimeType.enabledPlugin &&
-            (mimeType.enabledPlugin.name === plugin.name) &&
-            mimeType.type.indexOf(unversionedMimeType) !== -1) {
-
-          bits = extractVersion.exec(mimeType.type);
-
-          if (bits !== null && versionGreaterThan(bits[1], installedVersion)) {
-            installedVersion = bits[1];
-          }
-        }
-      }
-    }
-    else {
-      // This may mean that the installer plugin is not installed.
-      // Although it could also mean that we're on IE 9 and below,
-      // which does not support navigator.plugins. Fallback to
-      // using 'ActiveXObject' instead.
-      try {
-        plugin = new ActiveXObject(activeXControlId);
-        installedVersion = plugin.getMasterVersion();
-      } catch(e) {
-      }
-    }
-
-    updaterMimeType = installedVersion !== -1 ?
-                              unversionedMimeType + ',version=' + installedVersion :
-                              null;
-  };
-
-  var getInstallerMimeType = function getInstallerMimeType () {
-    if (updaterMimeType === void 0) {
-      findMimeTypeAndVersion();
-    }
-
-    return updaterMimeType;
-  };
-
-  var getInstalledVersion = function getInstalledVersion () {
-    if (installedVersion === void 0) {
-      findMimeTypeAndVersion();
-    }
-
-    return installedVersion;
-  };
-
-  // Version 0.4.0.4 autoupdate was broken. We want to prompt
-  // for install on 0.4.0.4 or earlier. We're also including
-  // earlier versions just in case...
-  var hasBrokenUpdater = function () {
-    var _broken = !versionGreaterThan(getInstalledVersion(), '0.4.0.4');
-
-    hasBrokenUpdater = function() { return _broken; };
-    return _broken;
-  };
-
-
-  AutoUpdater = function (plugin) {
-
-    // Returns true if the version of the plugin installed on this computer
-    // does not match the one expected by this version of TBPlugin.
-    this.isOutOfDate = function () {
-      return versionGreaterThan(pluginInfo.version, getInstalledVersion());
-    };
-
-    this.autoUpdate = function () {
-      var modal = OT.Dialogs.Plugin.updateInProgress(),
-          analytics = new OT.Analytics(),
-        payload = {
-          ieVersion: OT.$.browserVersion().version,
-          pluginOldVersion: TBPlugin.installedVersion(),
-          pluginNewVersion: TBPlugin.version()
-        };
-
-      var success = curryCallAsync(function() {
-            analytics.logEvent({
-              action: 'OTPluginAutoUpdate',
-              variation: 'Success',
-              partnerId: OT.APIKEY,
-              payload: JSON.stringify(payload)
-            });
-
-            plugin.destroy();
-
-            modal.close();
-            OT.Dialogs.Plugin.updateComplete().on({
-              reload: function() {
-                window.location.reload();
-              }
-            });
-          }),
-
-          error = curryCallAsync(function(errorCode, errorMessage, systemErrorCode) {
-            payload.errorCode = errorCode;
-            payload.systemErrorCode = systemErrorCode;
-
-            analytics.logEvent({
-              action: 'OTPluginAutoUpdate',
-              variation: 'Failure',
-              partnerId: OT.APIKEY,
-              payload: JSON.stringify(payload)
-            });
-
-            plugin.destroy();
-
-            modal.close();
-            var updateMessage = errorMessage + ' (' + errorCode +
-                                      '). Please restart your browser and try again.';
-
-            modal = OT.Dialogs.Plugin.updateComplete(updateMessage).on({
-              'reload': function() {
-                modal.close();
-              }
-            });
-
-            OT.error('autoUpdate failed: ' + errorMessage + ' (' + errorCode +
-                                      '). Please restart your browser and try again.');
-            // TODO log client event
-          }),
-
-          progress = curryCallAsync(function(progress) {
-            modal.setUpdateProgress(progress.toFixed());
-            // modalBody.innerHTML = 'Updating...' + progress.toFixed() + '%';
-          });
-
-      plugin._.updatePlugin(TBPlugin.pathToInstaller(), success, error, progress);
-    };
-
-    this.destroy = function() {
-      plugin.destroy();
-    };
-  };
-
-  AutoUpdater.get = function (completion) {
-    if (autoUpdaterController) {
-      completion.call(null, void 0, autoUpdaterController);
-      return;
-    }
-
-    if (!this.isinstalled()) {
-      completion.call(null, 'Plugin was not installed');
-      return;
-    }
-
-    injectObject(getInstallerMimeType(), false, {windowless: false}, function(err, plugin) {
-      if (plugin) autoUpdaterController = new AutoUpdater(plugin);
-      completion.call(null, err, autoUpdaterController);
-    });
-  };
-
-  AutoUpdater.isinstalled = function () {
-    return getInstallerMimeType() !== null && !hasBrokenUpdater();
-  };
-
-  AutoUpdater.installedVersion = function () {
-    return getInstalledVersion();
-  };
-
-})();
-
-// tb_require('./header.js')
-// tb_require('./shims.js')
-// tb_require('./plugin_object.js')
-// tb_require('./video_container.js')
-
-/* jshint globalstrict: true, strict: false, undef: true, unused: true,
-          trailing: true, browser: true, smarttabs:true */
-/* global OT:true, VideoContainer:true */
-/* exported MediaStream */
-
-var MediaStreamTrack = function MediaStreamTrack (mediaStreamId, options, plugin) {
-  this.id = options.id;
-  this.kind = options.kind;
-  this.label = options.label;
-  this.enabled = OT.$.castToBoolean(options.enabled);
-  this.streamId = mediaStreamId;
-
-  this.setEnabled = function (enabled) {
-    this.enabled = OT.$.castToBoolean(enabled);
-
-    if (this.enabled) {
-      plugin._.enableMediaStreamTrack(mediaStreamId, this.id);
-    }
-    else {
-      plugin._.disableMediaStreamTrack(mediaStreamId, this.id);
-    }
-  };
-};
-
-var MediaStream = function MediaStream (options, plugin) {
-  var audioTracks = [],
-      videoTracks = [];
-
-  this.id = options.id;
-  plugin.addRef(this);
-
-  // TODO
-  // this.ended =
-  // this.onended =
-
-  if (options.videoTracks) {
-    options.videoTracks.map(function(track) {
-      videoTracks.push( new MediaStreamTrack(options.id, track, plugin) );
-    });
-  }
-
-  if (options.audioTracks) {
-    options.audioTracks.map(function(track) {
-      audioTracks.push( new MediaStreamTrack(options.id, track, plugin) );
-    });
-  }
-
-  var hasTracksOfType = function (type) {
-    var tracks = type === 'video' ? videoTracks : audioTracks;
-
-    return OT.$.some(tracks, function(track) {
-      return track.enabled;
-    });
-  };
-
-  this.getVideoTracks = function () { return videoTracks; };
-  this.getAudioTracks = function () { return audioTracks; };
-
-  this.getTrackById = function (id) {
-    videoTracks.concat(audioTracks).forEach(function(track) {
-      if (track.id === id) return track;
-    });
-
-    return null;
-  };
-
-  this.hasVideo = function () {
-    return hasTracksOfType('video');
-  };
-
-  this.hasAudio = function () {
-    return hasTracksOfType('audio');
-  };
-
-  this.addTrack = function (/* MediaStreamTrack */) {
-    // TODO
-  };
-
-  this.removeTrack = function (/* MediaStreamTrack */) {
-    // TODO
-  };
-
-  this.stop = function() {
-    plugin._.stopMediaStream(this.id);
-    plugin.removeRef(this);
-  };
-
-  this.destroy = function() {
-    this.stop();
-  };
-
-  // Private MediaStream API
-  this._ = {
-    plugin: plugin,
-
-    // Get a VideoContainer to render the stream in.
-    render: OT.$.bind(function() {
-      return new VideoContainer(plugin, this);
-    }, this)
-  };
-};
-
-
-MediaStream.fromJson = function (json, plugin) {
-  if (!json) return null;
-  return new MediaStream( JSON.parse(json), plugin );
-};
-
-// tb_require('./header.js')
-// tb_require('./shims.js')
-
-/* global OT:true */
-/* exported PluginRumorSocket */
-
-var PluginRumorSocket = function(plugin, server) {
-  var connected = false,
-      rumorID;
-
-  try {
-    rumorID = plugin._.RumorInit(server, '');
-  }
-  catch(e) {
-    OT.error('Error creating the Rumor Socket: ', e.message);
-  }
-
-  if(!rumorID) {
-    throw new Error('Could not initialise plugin rumor connection');
-  }
-
-  var socket = {
-    open: function() {
-      connected = true;
-      plugin._.RumorOpen(rumorID);
-    },
-
-    close: function(code, reason) {
-      if (!connected) return;
-      connected = false;
-
-      plugin._.RumorClose(rumorID, code, reason);
-      plugin.removeRef(this);
-    },
-
-    destroy: function() {
-      this.close();
-    },
-
-    send: function(msg) {
-      plugin._.RumorSend(rumorID, msg.type, msg.toAddress,
-        JSON.parse(JSON.stringify(msg.headers)), msg.data);
-    },
-
-    onOpen: function(callback) {
-      plugin._.SetOnRumorOpen(rumorID, callback);
-    },
-
-    onClose: function(callback) {
-      plugin._.SetOnRumorClose(rumorID, callback);
-    },
-
-    onError: function(callback) {
-      plugin._.SetOnRumorError(rumorID, callback);
-    },
-
-    onMessage: function(callback) {
-      plugin._.SetOnRumorMessage(rumorID, callback);
-    }
-  };
-
-  plugin.addRef(socket);
-  return socket;
-
-};
-
-// tb_require('./header.js')
-// tb_require('./shims.js')
-// tb_require('./plugin_object.js')
-// tb_require('./video_container.js')
-
-/* jshint globalstrict: true, strict: false, undef: true, unused: true,
-          trailing: true, browser: true, smarttabs:true */
-/* global OT:true */
-/* exported MediaConstraints */
-
-var MediaConstraints = function(userConstraints) {
-  var constraints = OT.$.clone(userConstraints);
-
-  this.hasVideo = constraints.video !== void 0 && constraints.video !== false;
-  this.hasAudio = constraints.audio !== void 0 && constraints.audio !== false;
-
-  if (constraints.video === true) constraints.video = {};
-  if (constraints.audio === true)  constraints.audio = {};
-
-  if (this.hasVideo && !constraints.video.mandatory) {
-    constraints.video.mandatory = {};
-  }
-
-  if (this.hasAudio && !constraints.audio.mandatory) {
-    constraints.audio.mandatory = {};
-  }
-
-  this.screenSharing = this.hasVideo &&
-                ( constraints.video.mandatory.chromeMediaSource === 'screen' ||
-                  constraints.video.mandatory.chromeMediaSource === 'window' );
-
-  this.audio = constraints.audio;
-  this.video = constraints.video;
-
-  this.setVideoSource = function(sourceId) {
-    if (sourceId !== void 0) constraints.video.mandatory.sourceId =  sourceId;
-    else delete constraints.video;
-  };
-
-  this.setAudioSource = function(sourceId) {
-    if (sourceId !== void 0) constraints.audio.mandatory.sourceId =  sourceId;
-    else delete constraints.audio;
-  };
-
-  this.toHash = function() {
-    return constraints;
-  };
-};
-
-// tb_require('./header.js')
-// tb_require('./shims.js')
-// tb_require('./plugin_object.js')
-
-/* jshint globalstrict: true, strict: false, undef: true, unused: true,
-          trailing: true, browser: true, smarttabs:true */
 /* exported RTCStatsReport */
 
 var RTCStatsReport = function (reports) {
   this.forEach = function (callback, context) {
     for (var id in reports) {
       callback.call(context, reports[id]);
     }
   };
@@ -5033,16 +4483,404 @@ var PeerConnection = function PeerConnec
 };
 
 
 
 
 // tb_require('./header.js')
 // tb_require('./shims.js')
 // tb_require('./plugin_object.js')
+// tb_require('./video_container.js')
+
+/* jshint globalstrict: true, strict: false, undef: true, unused: true,
+          trailing: true, browser: true, smarttabs:true */
+/* global OT:true, VideoContainer:true */
+/* exported MediaStream */
+
+var MediaStreamTrack = function MediaStreamTrack (mediaStreamId, options, plugin) {
+  this.id = options.id;
+  this.kind = options.kind;
+  this.label = options.label;
+  this.enabled = OT.$.castToBoolean(options.enabled);
+  this.streamId = mediaStreamId;
+
+  this.setEnabled = function (enabled) {
+    this.enabled = OT.$.castToBoolean(enabled);
+
+    if (this.enabled) {
+      plugin._.enableMediaStreamTrack(mediaStreamId, this.id);
+    }
+    else {
+      plugin._.disableMediaStreamTrack(mediaStreamId, this.id);
+    }
+  };
+};
+
+var MediaStream = function MediaStream (options, plugin) {
+  var audioTracks = [],
+      videoTracks = [];
+
+  this.id = options.id;
+  plugin.addRef(this);
+
+  // TODO
+  // this.ended =
+  // this.onended =
+
+  if (options.videoTracks) {
+    options.videoTracks.map(function(track) {
+      videoTracks.push( new MediaStreamTrack(options.id, track, plugin) );
+    });
+  }
+
+  if (options.audioTracks) {
+    options.audioTracks.map(function(track) {
+      audioTracks.push( new MediaStreamTrack(options.id, track, plugin) );
+    });
+  }
+
+  var hasTracksOfType = function (type) {
+    var tracks = type === 'video' ? videoTracks : audioTracks;
+
+    return OT.$.some(tracks, function(track) {
+      return track.enabled;
+    });
+  };
+
+  this.getVideoTracks = function () { return videoTracks; };
+  this.getAudioTracks = function () { return audioTracks; };
+
+  this.getTrackById = function (id) {
+    videoTracks.concat(audioTracks).forEach(function(track) {
+      if (track.id === id) return track;
+    });
+
+    return null;
+  };
+
+  this.hasVideo = function () {
+    return hasTracksOfType('video');
+  };
+
+  this.hasAudio = function () {
+    return hasTracksOfType('audio');
+  };
+
+  this.addTrack = function (/* MediaStreamTrack */) {
+    // TODO
+  };
+
+  this.removeTrack = function (/* MediaStreamTrack */) {
+    // TODO
+  };
+
+  this.stop = function() {
+    plugin._.stopMediaStream(this.id);
+    plugin.removeRef(this);
+  };
+
+  this.destroy = function() {
+    this.stop();
+  };
+
+  // Private MediaStream API
+  this._ = {
+    plugin: plugin,
+
+    // Get a VideoContainer to render the stream in.
+    render: OT.$.bind(function() {
+      return new VideoContainer(plugin, this);
+    }, this)
+  };
+};
+
+
+MediaStream.fromJson = function (json, plugin) {
+  if (!json) return null;
+  return new MediaStream( JSON.parse(json), plugin );
+};
+
+// tb_require('./header.js')
+// tb_require('./shims.js')
+// tb_require('./plugin_object.js')
+// tb_require('./video_container.js')
+
+/* jshint globalstrict: true, strict: false, undef: true, unused: true,
+          trailing: true, browser: true, smarttabs:true */
+/* global OT:true */
+/* exported MediaConstraints */
+
+var MediaConstraints = function(userConstraints) {
+  var constraints = OT.$.clone(userConstraints);
+
+  this.hasVideo = constraints.video !== void 0 && constraints.video !== false;
+  this.hasAudio = constraints.audio !== void 0 && constraints.audio !== false;
+
+  if (constraints.video === true) constraints.video = {};
+  if (constraints.audio === true)  constraints.audio = {};
+
+  if (this.hasVideo && !constraints.video.mandatory) {
+    constraints.video.mandatory = {};
+  }
+
+  if (this.hasAudio && !constraints.audio.mandatory) {
+    constraints.audio.mandatory = {};
+  }
+
+  this.screenSharing = this.hasVideo &&
+                ( constraints.video.mandatory.chromeMediaSource === 'screen' ||
+                  constraints.video.mandatory.chromeMediaSource === 'window' );
+
+  this.audio = constraints.audio;
+  this.video = constraints.video;
+
+  this.setVideoSource = function(sourceId) {
+    if (sourceId !== void 0) constraints.video.mandatory.sourceId =  sourceId;
+    else delete constraints.video;
+  };
+
+  this.setAudioSource = function(sourceId) {
+    if (sourceId !== void 0) constraints.audio.mandatory.sourceId =  sourceId;
+    else delete constraints.audio;
+  };
+
+  this.toHash = function() {
+    return constraints;
+  };
+};
+
+// tb_require('./header.js')
+// tb_require('./shims.js')
+// tb_require('./plugin_object.js')
+
+/* jshint globalstrict: true, strict: false, undef: true, unused: true,
+          trailing: true, browser: true, smarttabs:true */
+/* global OT:true, TBPlugin:true, pluginInfo:true, ActiveXObject:true,
+          injectObject:true, curryCallAsync:true */
+
+/* exported AutoUpdater:true */
+var AutoUpdater;
+
+(function() {
+
+  var autoUpdaterController,
+      updaterMimeType,        // <- cached version, use getInstallerMimeType instead
+      installedVersion = -1;  // <- cached version, use getInstallerMimeType instead
+
+
+  var versionGreaterThan = function versionGreaterThan (version1,version2) {
+    if (version1 === version2) return false;
+
+    var v1 = version1.split('.'),
+        v2 = version2.split('.');
+
+    v1 = parseFloat(parseInt(v1.shift(), 10) + '.' +
+                      v1.map(function(vcomp) { return parseInt(vcomp, 10); }).join(''));
+
+    v2 = parseFloat(parseInt(v2.shift(), 10) + '.' +
+                      v2.map(function(vcomp) { return parseInt(vcomp, 10); }).join(''));
+
+
+    return v1 > v2;
+  };
+
+
+  // Work out the full mimeType (including the currently installed version)
+  // of the installer.
+  var findMimeTypeAndVersion = function findMimeTypeAndVersion () {
+
+    if (updaterMimeType !== void 0) {
+      return updaterMimeType;
+    }
+
+    var activeXControlId = 'TokBox.otiePluginInstaller',
+        unversionedMimeType = 'application/x-otieplugininstaller',
+        plugin = navigator.plugins[activeXControlId];
+
+    installedVersion = -1;
+
+
+    if (plugin) {
+      // Look through the supported mime-types for the version
+      // There should only be one mime-type in our use case, and
+      // if there's more than one they should all have the same
+      // version.
+      var numMimeTypes = plugin.length,
+          extractVersion = new RegExp(unversionedMimeType.replace('-', '\\-') +
+                                                            ',version=([0-9]+)', 'i'),
+          mimeType,
+          bits;
+
+      for (var i=0; i<numMimeTypes; ++i) {
+        mimeType = plugin[i];
+
+        // Look through the supported mimeTypes and find
+        // the newest one.
+        if (mimeType && mimeType.enabledPlugin &&
+            (mimeType.enabledPlugin.name === plugin.name) &&
+            mimeType.type.indexOf(unversionedMimeType) !== -1) {
+
+          bits = extractVersion.exec(mimeType.type);
+
+          if (bits !== null && versionGreaterThan(bits[1], installedVersion)) {
+            installedVersion = bits[1];
+          }
+        }
+      }
+    }
+    else {
+      // This may mean that the installer plugin is not installed.
+      // Although it could also mean that we're on IE 9 and below,
+      // which does not support navigator.plugins. Fallback to
+      // using 'ActiveXObject' instead.
+      try {
+        plugin = new ActiveXObject(activeXControlId);
+        installedVersion = plugin.getMasterVersion();
+      } catch(e) {
+      }
+    }
+
+    updaterMimeType = installedVersion !== -1 ?
+                              unversionedMimeType + ',version=' + installedVersion :
+                              null;
+  };
+
+  var getInstallerMimeType = function getInstallerMimeType () {
+    if (updaterMimeType === void 0) {
+      findMimeTypeAndVersion();
+    }
+
+    return updaterMimeType;
+  };
+
+  var getInstalledVersion = function getInstalledVersion () {
+    if (installedVersion === void 0) {
+      findMimeTypeAndVersion();
+    }
+
+    return installedVersion;
+  };
+
+  // Version 0.4.0.4 autoupdate was broken. We want to prompt
+  // for install on 0.4.0.4 or earlier. We're also including
+  // earlier versions just in case...
+  var hasBrokenUpdater = function () {
+    var _broken = !versionGreaterThan(getInstalledVersion(), '0.4.0.4');
+
+    hasBrokenUpdater = function() { return _broken; };
+    return _broken;
+  };
+
+
+  AutoUpdater = function (plugin) {
+
+    // Returns true if the version of the plugin installed on this computer
+    // does not match the one expected by this version of TBPlugin.
+    this.isOutOfDate = function () {
+      return versionGreaterThan(pluginInfo.version, getInstalledVersion());
+    };
+
+    this.autoUpdate = function () {
+      var modal = OT.Dialogs.Plugin.updateInProgress(),
+          analytics = new OT.Analytics(),
+        payload = {
+          ieVersion: OT.$.browserVersion().version,
+          pluginOldVersion: TBPlugin.installedVersion(),
+          pluginNewVersion: TBPlugin.version()
+        };
+
+      var success = curryCallAsync(function() {
+            analytics.logEvent({
+              action: 'OTPluginAutoUpdate',
+              variation: 'Success',
+              partnerId: OT.APIKEY,
+              payload: JSON.stringify(payload)
+            });
+
+            plugin.destroy();
+
+            modal.close();
+            OT.Dialogs.Plugin.updateComplete().on({
+              reload: function() {
+                window.location.reload();
+              }
+            });
+          }),
+
+          error = curryCallAsync(function(errorCode, errorMessage, systemErrorCode) {
+            payload.errorCode = errorCode;
+            payload.systemErrorCode = systemErrorCode;
+
+            analytics.logEvent({
+              action: 'OTPluginAutoUpdate',
+              variation: 'Failure',
+              partnerId: OT.APIKEY,
+              payload: JSON.stringify(payload)
+            });
+
+            plugin.destroy();
+
+            modal.close();
+            var updateMessage = errorMessage + ' (' + errorCode +
+                                      '). Please restart your browser and try again.';
+
+            modal = OT.Dialogs.Plugin.updateComplete(updateMessage).on({
+              'reload': function() {
+                modal.close();
+              }
+            });
+
+            OT.error('autoUpdate failed: ' + errorMessage + ' (' + errorCode +
+                                      '). Please restart your browser and try again.');
+            // TODO log client event
+          }),
+
+          progress = curryCallAsync(function(progress) {
+            modal.setUpdateProgress(progress.toFixed());
+            // modalBody.innerHTML = 'Updating...' + progress.toFixed() + '%';
+          });
+
+      plugin._.updatePlugin(TBPlugin.pathToInstaller(), success, error, progress);
+    };
+
+    this.destroy = function() {
+      plugin.destroy();
+    };
+  };
+
+  AutoUpdater.get = function (completion) {
+    if (autoUpdaterController) {
+      completion.call(null, void 0, autoUpdaterController);
+      return;
+    }
+
+    if (!this.isinstalled()) {
+      completion.call(null, 'Plugin was not installed');
+      return;
+    }
+
+    injectObject(getInstallerMimeType(), false, {windowless: false}, function(err, plugin) {
+      if (plugin) autoUpdaterController = new AutoUpdater(plugin);
+      completion.call(null, err, autoUpdaterController);
+    });
+  };
+
+  AutoUpdater.isinstalled = function () {
+    return getInstallerMimeType() !== null && !hasBrokenUpdater();
+  };
+
+  AutoUpdater.installedVersion = function () {
+    return getInstalledVersion();
+  };
+
+})();
+
+// tb_require('./header.js')
+// tb_require('./shims.js')
+// tb_require('./plugin_object.js')
 // tb_require('./auto_updater.js')
 // tb_require('./media_constraints.js')
 // tb_require('./peer_connection.js')
 // tb_require('./media_stream.js')
 // tb_require('./video_container.js')
 // tb_require('./rumor.js')
 
 /* jshint globalstrict: true, strict: false, undef: true,
@@ -7012,17 +6850,17 @@ waitForDomReady();
         // Map of camel-cased keys to underscored
         camelCasedKeys,
 
         browser = OT.$.browserVersion(),
 
         send = function(data, isQos, callback) {
           OT.$.post((isQos ? endPointQos : endPoint) + '?_=' + OT.$.uuid.v4(), {
             body: data,
-            xdomainrequest: (browser.browser === 'IE' & browser.version < 10),
+            xdomainrequest: (browser.browser === 'IE' && browser.version < 10),
             headers: {
               'Content-Type': 'application/x-www-form-urlencoded'
             }
           }, callback);
         },
 
         throttledPost = function() {
           // Throttle logs so that they only happen 1 at a time
@@ -9168,17 +9006,17 @@ waitForDomReady();
  *
  * Original source: https://github.com/inexorabletash/text-encoding
  ***/
 
 (function(global) {
   'use strict';
 
   var browser = OT.$.browserVersion();
-  if(browser.browser === 'IE' && browser.version < 10) {
+  if(browser && browser.browser === 'IE' && browser.version < 10) {
     return; // IE 8 doesn't do websockets. No websockets, no encoding.
   }
 
   if ( (global.TextEncoder !== void 0) && (global.TextDecoder !== void 0))  {
     // defer to the native ones
     // @todo is this a good idea?
     return;
   }
@@ -13533,18 +13371,20 @@ waitForDomReady();
     if (session.archives.has(dict.id)) return;
 
     var archive = parseArchive(dict);
     session.archives.add(archive);
 
     return archive;
   }
 
-  var sessionRead;
-  var sessionReadQueue = [];
+  var sessionRead,
+    sessionReadQueue = [],
+    // streams for which corresponding connectionCreated events have not been dispatched:
+    unconnectedStreams = {};
 
   function sessionReadQueuePush(type, args) {
     var triggerArgs = ['signal'];
     triggerArgs.push.apply(triggerArgs, args);
     sessionReadQueue.push(triggerArgs);
   }
 
   window.OT.SessionDispatcher = function(session) {
@@ -13604,25 +13444,39 @@ waitForDomReady();
 
     });
 
     dispatcher.on('connection#created', function(connection) {
       connection = OT.Connection.fromHash(connection);
       if (session.connection && connection.id !== session.connection.id) {
         session.connections.add( connection );
       }
+
+      OT.$.forEach(OT.$.keys(unconnectedStreams), function(streamId) {
+        var stream = unconnectedStreams[streamId];
+        if (stream && connection.id === stream.connection.id) {
+          // dispatch streamCreated event now that the connectionCreated has been dispatched
+          parseAndAddStreamToSession(stream, session);
+          delete unconnectedStreams[stream.id];
+        }
+      });
     });
 
     dispatcher.on('connection#deleted', function(connection, reason) {
       connection = session.connections.get(connection);
       connection.destroy(reason);
     });
 
     dispatcher.on('stream#created', function(stream, transactionId) {
-      stream = parseAndAddStreamToSession(stream, session);
+      var connectionId = stream.connectionId ? stream.connectionId : stream.connection.id;
+      if (session.connections.has(connectionId)) {
+        stream = parseAndAddStreamToSession(stream, session);
+      } else {
+        unconnectedStreams[stream.id] = stream;
+      }
 
       if (stream.publisher) {
         stream.publisher.setStream(stream);
       }
 
       dispatcher.triggerCallback(transactionId, null, stream);
     });
 
@@ -14767,16 +14621,32 @@ waitForDomReady();
 
     this.destroy = function() {};
 
   };
 
 })(window);
 !(function() {
 
+  /**
+   * Lazy instantiates an audio context and always return the same instance on following calls
+   *
+   * @returns {AudioContext}
+   */
+  OT.audioContext = function() {
+    var context = new window.AudioContext();
+    OT.audioContext = function() {
+      return context;
+    };
+    return context;
+  };
+
+})();
+!(function() {
+
 
   /*
    * A <code>RTCPeerConnection.getStats</code> based audio level sampler.
    *
    * It uses the the <code>getStats</code> method to get the <code>audioOutputLevel</code>.
    * This implementation expects the single parameter version of the <code>getStats</code> method.
    *
    * Currently the <code>audioOutputLevel</code> stats is only supported in Chrome.
@@ -14929,17 +14799,150 @@ waitForDomReady();
 
     this.stop = function() {
       window.clearInterval(_intervalId);
       _intervalId = null;
     };
   };
 
 })(window);
+// tb_require('../../helpers/helpers.js')
+
+/* jshint globalstrict: true, strict: false, undef: true, unused: true,
+          trailing: true, browser: true, smarttabs:true */
+/* global OT */
+/* exported SDPHelpers */
+
+var findIndex = function(array, iter, ctx) {
+  if (!OT.$.isFunction(iter)) {
+    throw new TypeError('iter must be a function');
+  }
+
+  for (var i = 0, count = array.length || 0; i < count; ++i) {
+    if (i in array && iter.call(ctx, array[i], i, array)) {
+      return i;
+    }
+  }
+
+  return -1;
+};
+
+// Here are the structure of the rtpmap attribute and the media line, most of the
+// complex Regular Expressions in this code are matching against one of these two
+// formats:
+// * a=rtpmap:<payload type> <encoding name>/<clock rate> [/<encoding parameters>]
+// * m=<media> <port>/<number of ports> <proto> <fmts>
+//
+// References:
+// * https://tools.ietf.org/html/rfc4566
+// * http://en.wikipedia.org/wiki/Session_Description_Protocol
+//
+var SDPHelpers = {
+  // Search through sdpLines to find the Media Line of type +mediaType+.
+  getMLineIndex: function getMLineIndex(sdpLines, mediaType) {
+    var targetMLine = 'm=' + mediaType;
+
+    // Find the index of the media line for +type+
+    return findIndex(sdpLines, function(line) {
+      if (line.indexOf(targetMLine) !== -1) {
+        return true;
+      }
+
+      return false;
+    });
+  },
+
+  // Extract the payload types for a give Media Line.
+  //
+  getMLinePayloadTypes: function getMLinePayloadTypes (mediaLine, mediaType) {
+    var mLineSelector = new RegExp('^m=' + mediaType +
+                          ' \\d+(/\\d+)? [a-zA-Z0-9/]+(( [a-zA-Z0-9/]+)+)$', 'i');
+
+    // Get all payload types that the line supports
+    var payloadTypes = mediaLine.match(mLineSelector);
+    if (!payloadTypes || payloadTypes.length < 2) {
+      // Error, invalid M line?
+      return [];
+    }
+
+    return OT.$.trim(payloadTypes[2]).split(' ');
+  },
+
+  removeTypesFromMLine: function removeTypesFromMLine (mediaLine, payloadTypes) {
+    return mediaLine.replace(new RegExp(' ' + payloadTypes.join(' |'), 'ig') , '')
+                    .replace(/\s+/g, ' ');
+  },
+
+
+  // Remove all references to a particular encodingName from a particular media type
+  //
+  removeMediaEncoding: function removeMediaEncoding (sdp, mediaType, encodingName) {
+    var sdpLines = sdp.split('\r\n'),
+        mLineIndex = SDPHelpers.getMLineIndex(sdpLines, mediaType),
+        mLine = mLineIndex > -1 ? sdpLines[mLineIndex] : void 0,
+        typesToRemove = [],
+        payloadTypes,
+        match;
+
+    if (mLineIndex === -1) {
+      // Error, missing M line
+      return sdpLines.join('\r\n');
+    }
+
+    // Get all payload types that the line supports
+    payloadTypes = SDPHelpers.getMLinePayloadTypes(mLine, mediaType);
+    if (payloadTypes.length === 0) {
+      // Error, invalid M line?
+      return sdpLines.join('\r\n');
+    }
+
+    // Find the location of all the rtpmap lines that relate to +encodingName+
+    // and any of the supported payload types
+    var matcher = new RegExp('a=rtpmap:(' + payloadTypes.join('|') + ') ' +
+                                          encodingName + '\\/\\d+', 'i');
+
+    sdpLines = OT.$.filter(sdpLines, function(line, index) {
+      match = line.match(matcher);
+      if (match === null) return true;
+
+      typesToRemove.push(match[1]);
+
+      if (index < mLineIndex) {
+        // This removal changed the index of the mline, track it
+        mLineIndex--;
+      }
+
+      // remove this one
+      return false;
+    });
+
+    if (typesToRemove.length > 0 && mLineIndex > -1) {
+      // Remove all the payload types and we've removed from the media line
+      sdpLines[mLineIndex] = SDPHelpers.removeTypesFromMLine(mLine, typesToRemove);
+    }
+
+    return sdpLines.join('\r\n');
+  },
+
+  // Removes all Confort Noise from +sdp+.
+  //
+  // See https://jira.tokbox.com/browse/OPENTOK-7176
+  //
+  removeComfortNoise: function removeComfortNoise (sdp) {
+    return SDPHelpers.removeMediaEncoding(sdp, 'audio', 'CN');
+  },
+
+  removeVideoCodec: function removeVideoCodec (sdp, codec) {
+    return SDPHelpers.removeMediaEncoding(sdp, 'video', codec);
+  }
+};
+
+
 !(function(window) {
+  /* global SDPHelpers */
 
   // Normalise these
   var NativeRTCSessionDescription,
       NativeRTCIceCandidate;
 
   if (!TBPlugin.isInstalled()) {
     // order is very important: 'RTCSessionDescription' defined in Firefox Nighly but useless
     NativeRTCSessionDescription = (window.mozRTCSessionDescription ||
@@ -14998,84 +15001,16 @@ waitForDomReady();
 
     this.processPending = function() {
       while(_pendingIceCandidates.length) {
         _peerConnection.addIceCandidate(_pendingIceCandidates.shift());
       }
     };
   };
 
-  // Removes all Confort Noise from +sdp+.
-  //
-  // See https://jira.tokbox.com/browse/OPENTOK-7176
-  //
-  var removeComfortNoise = function removeComfortNoise (sdp) {
-    // a=rtpmap:<payload type> <encoding name>/<clock rate> [/<encoding parameters>]
-    var matcher = /a=rtpmap:(\d+) CN\/\d+/i,
-        payloadTypes = [],
-        audioMediaLineIndex,
-        sdpLines,
-        match;
-
-    // Icky code. This filter operation has two side effects in addition
-    // to doing the actual filtering:
-    //   1. extract all the payload types from the rtpmap CN lines
-    //   2. find the index of the audio media line
-    //
-    sdpLines = OT.$.filter(sdp.split('\r\n'), function(line, index) {
-      if (line.indexOf('m=audio') !== -1) audioMediaLineIndex = index;
-
-      match = line.match(matcher);
-      if (match !== null) {
-        payloadTypes.push(match[1]);
-
-        // remove this line as it contains CN
-        return false;
-      }
-
-      return true;
-    });
-
-    if (payloadTypes.length && audioMediaLineIndex) {
-      // Remove all CN payload types from the audio media line.
-      sdpLines[audioMediaLineIndex] = sdpLines[audioMediaLineIndex].replace(
-        new RegExp(payloadTypes.join('|'), 'ig') , '').replace(/\s+/g, ' ');
-    }
-
-    return sdpLines.join('\r\n');
-  };
-
-  var removeVideoCodec = function removeVideoCodec (sdp, codec) {
-    var matcher =  new RegExp('a=rtpmap:(\\d+) ' + codec + '\\/\\d+', 'i'),
-        payloadTypes = [],
-        videoMediaLineIndex,
-        sdpLines,
-        match;
-
-    sdpLines = OT.$.filter(sdp.split('\r\n'), function(line, index) {
-      if (line.indexOf('m=video') !== -1) videoMediaLineIndex = index;
-
-      match = line.match(matcher);
-      if (match !== null) {
-        payloadTypes.push(match[1]);
-
-        // remove this line as it contains the codec
-        return false;
-      }
-
-      return true;
-    });
-
-    if (payloadTypes.length && videoMediaLineIndex) {
-      sdpLines[videoMediaLineIndex] = sdpLines[videoMediaLineIndex].replace(
-        new RegExp(payloadTypes.join('|'), 'ig') , '').replace(/\s+/g, ' ');
-    }
-
-    return sdpLines.join('\r\n');
-  };
 
   // Attempt to completely process +offer+. This will:
   // * set the offer as the remote description
   // * create an answer and
   // * set the new answer as the location description
   //
   // If there are no issues, the +success+ callback will be executed on completion.
   // Errors during any step will result in the +failure+ callback being executed.
@@ -15090,19 +15025,19 @@ waitForDomReady();
         OT.error(message);
         OT.error(errorReason);
 
         if (failure) failure(message, errorReason, prefix);
       };
     };
 
     setLocalDescription = function(answer) {
-      answer.sdp = removeComfortNoise(answer.sdp);
-      answer.sdp = removeVideoCodec(answer.sdp, 'ulpfec');
-      answer.sdp = removeVideoCodec(answer.sdp, 'red');
+      answer.sdp = SDPHelpers.removeComfortNoise(answer.sdp);
+      answer.sdp = SDPHelpers.removeVideoCodec(answer.sdp, 'ulpfec');
+      answer.sdp = SDPHelpers.removeVideoCodec(answer.sdp, 'red');
 
       peerConnection.setLocalDescription(
         answer,
 
         // Success
         function() {
           success(answer);
         },
@@ -15176,19 +15111,20 @@ waitForDomReady();
         OT.error(message);
         OT.error(errorReason);
 
         if (failure) failure(message, errorReason, prefix);
       };
     };
 
     setLocalDescription = function(offer) {
-      offer.sdp = removeComfortNoise(offer.sdp);
-      offer.sdp = removeVideoCodec(offer.sdp, 'ulpfec');
-      offer.sdp = removeVideoCodec(offer.sdp, 'red');
+      offer.sdp = SDPHelpers.removeComfortNoise(offer.sdp);
+      offer.sdp = SDPHelpers.removeVideoCodec(offer.sdp, 'ulpfec');
+      offer.sdp = SDPHelpers.removeVideoCodec(offer.sdp, 'red');
+
 
       peerConnection.setLocalDescription(
         offer,
 
         // Success
         function() {
           success(offer);
         },
@@ -16581,52 +16517,39 @@ waitForDomReady();
   // Whether to display the name. Possible values are: "auto" (the name is displayed
   // when the stream is first displayed and when the user mouses over the display),
   // "off" (the name is not displayed), and "on" (the name is displayed).
   //
   // displays a name
   // can be shown/hidden
   // can be destroyed
   OT.Chrome.NamePanel = function(options) {
-    var _name = options.name,
-        _bugMode = options.bugMode;
+    var _name = options.name;
 
     if (!_name || OT.$.trim(_name).length === '') {
       _name = null;
 
       // THere's no name, just flip the mode off
       options.mode = 'off';
     }
 
     this.setName = OT.$.bind(function(name) {
       if (!_name) this.setDisplayMode('auto');
       _name = name;
       this.domElement.innerHTML = _name;
     });
 
-    this.setBugMode = OT.$.bind(function(bugMode) {
-      _bugMode = bugMode;
-      if(bugMode === 'off') {
-        OT.$.addClass(this.domElement, 'OT_name-no-bug');
-      } else {
-        OT.$.removeClass(this.domElement, 'OT_name-no-bug');
-      }
-    }, this);
-
     // Mixin common widget behaviour
     OT.Chrome.Behaviour.Widget(this, {
       mode: options.mode,
       nodeName: 'h1',
       htmlContent: _name,
       htmlAttributes: {
         className: 'OT_name OT_edge-bar-item'
-      },
-      onCreate: OT.$.bind(function() {
-        this.setBugMode(_bugMode);
-      }, this)
+      }
     });
 
   };
 
 })(window);
 !(function() {
 
   OT.Chrome.MuteButton = function(options) {
@@ -16693,33 +16616,16 @@ waitForDomReady();
       onDestroy: OT.$.bind(detachEvents, this)
     });
   };
 
 
 })(window);
 !(function() {
 
-  OT.Chrome.OpenTokButton = function(options) {
-
-    // Mixin common widget behaviour
-    OT.Chrome.Behaviour.Widget(this, {
-      mode: options ? options.mode : null,
-      nodeName: 'span',
-      htmlContent: 'OpenTok',
-      htmlAttributes: {
-        className: 'OT_opentok OT_edge-bar-item'
-      }
-    });
-
-  };
-
-})(window);
-!(function() {
-
   // Archving Chrome Widget
   //
   // mode (String)
   // Whether to display the archving widget. Possible values are: "on" (the status is displayed
   // when archiving and briefly when archving ends) and "off" (the status is not displayed)
 
   // Whether to display the archving widget. Possible values are: "auto" (the name is displayed
   // when the status is first displayed and when the user mouses over the display),
@@ -16831,45 +16737,60 @@ waitForDomReady();
     var widget = this,
         _meterBarElement,
         _voiceOnlyIconElement,
         _meterValueElement,
         _value,
         _maxValue = options.maxValue || 1,
         _minValue = options.minValue || 0;
 
+    function onCreate() {
+      _meterBarElement = OT.$.createElement('div', {
+        className: 'OT_audio-level-meter__bar'
+      }, '');
+      _meterValueElement = OT.$.createElement('div', {
+        className: 'OT_audio-level-meter__value'
+      }, '');
+      _voiceOnlyIconElement = OT.$.createElement('div', {
+        className: 'OT_audio-level-meter__audio-only-img'
+      }, '');
+
+      widget.domElement.appendChild(_meterBarElement);
+      widget.domElement.appendChild(_voiceOnlyIconElement);
+      widget.domElement.appendChild(_meterValueElement);
+    }
+
+    function updateView() {
+      var percentSize = _value * 100 / (_maxValue - _minValue);
+      _meterValueElement.style.width = _meterValueElement.style.height = 2 * percentSize + '%';
+      _meterValueElement.style.top = _meterValueElement.style.right = -percentSize + '%';
+    }
+
     // Mixin common widget behaviour
-    OT.Chrome.Behaviour.Widget(this, {
+    var widgetOptions = {
       mode: options ? options.mode : 'auto',
       nodeName: 'div',
       htmlAttributes: {
         className: 'OT_audio-level-meter'
       },
-      onCreate: function() {
-        _meterBarElement = OT.$.createElement('div', {
-          className: 'OT_audio-level-meter__bar'
-        }, '');
-        _meterValueElement = OT.$.createElement('div', {
-          className: 'OT_audio-level-meter__value'
-        }, '');
-        _voiceOnlyIconElement = OT.$.createElement('div', {
-          className: 'OT_audio-level-meter__audio-only-img'
-        }, '');
-
-        widget.domElement.appendChild(_meterBarElement);
-        widget.domElement.appendChild(_voiceOnlyIconElement);
-        widget.domElement.appendChild(_meterValueElement);
-      }
-    });
-
-    function updateView() {
-      var percentSize = _value * 100 / (_maxValue - _minValue);
-      _meterValueElement.style.width = _meterValueElement.style.height = 2 * percentSize + '%';
-      _meterValueElement.style.top = _meterValueElement.style.right = -percentSize + '%';
-    }
+      onCreate: onCreate
+    };
+
+    OT.Chrome.Behaviour.Widget(this, widgetOptions);
+
+    // override
+    var _setDisplayMode = OT.$.bind(widget.setDisplayMode, widget);
+    widget.setDisplayMode = function(mode) {
+      _setDisplayMode(mode);
+      if (mode === 'off') {
+        if (options.onPassivate) options.onPassivate();
+      } else {
+        if (options.onActivate) options.onActivate();
+      }
+    };
 
     widget.setValue = function(value) {
       _value = value;
       updateView();
     };
   };
 
 })(window);
@@ -17177,24 +17098,22 @@ waitForDomReady();
         isValidStyle,
         castValue;
 
     _COMPONENT_STYLES = [
       'showMicButton',
       'showSpeakerButton',
       'nameDisplayMode',
       'buttonDisplayMode',
-      'backgroundImageURI',
-      'bugDisplayMode'
+      'backgroundImageURI'
     ];
 
     _validStyleValues = {
       buttonDisplayMode: ['auto', 'mini', 'mini-auto', 'off', 'on'],
       nameDisplayMode: ['auto', 'off', 'on'],
-      bugDisplayMode: ['auto', 'off', 'on'],
       audioLevelDisplayMode: ['auto', 'off', 'on'],
       showSettingsButton: [true, false],
       showMicButton: [true, false],
       backgroundImageURI: null,
       showControlBar: [true, false],
       showArchiveStatus: [true, false],
       videoDisabledDisplayMode: ['auto', 'off', 'on']
     };
@@ -17688,38 +17607,38 @@ waitForDomReady();
         _audioLevelMeter,
         _analytics = new OT.Analytics(),
         _validResolutions,
         _validFrameRates = [ 1, 7, 15, 30 ],
         _prevStats,
         _state,
         _iceServers,
         _audioLevelCapable = OT.$.hasCapabilities('webAudio'),
-        _audioLevelSampler;
+        _audioLevelSampler,
+        _publisher = this;
 
     _validResolutions = {
       '320x240': {width: 320, height: 240},
       '640x480': {width: 640, height: 480},
       '1280x720': {width: 1280, height: 720}
     };
 
     _prevStats = {
       'timeStamp' : OT.$.now()
     };
 
     OT.$.eventing(this);
 
     if(_audioLevelCapable) {
-      _audioLevelSampler = new OT.AnalyserAudioLevelSampler(new window.AudioContext());
-
-      var publisher = this;
+      _audioLevelSampler = new OT.AnalyserAudioLevelSampler(OT.audioContext());
+
       var audioLevelRunner = new OT.IntervalRunner(function() {
         _audioLevelSampler.sample(function(audioInputLevel) {
           OT.$.requestAnimationFrame(function() {
-            publisher.dispatchEvent(
+            _publisher.dispatchEvent(
               new OT.AudioLevelUpdatedEvent(audioInputLevel));
           });
         });
       }, 60);
 
       this.on({
         'audioLevelUpdated:added': function(count) {
           if (count === 1) {
@@ -17733,17 +17652,16 @@ waitForDomReady();
         }
       });
     }
 
     OT.StylableComponent(this, {
       showArchiveStatus: true,
       nameDisplayMode: 'auto',
       buttonDisplayMode: 'auto',
-      bugDisplayMode: 'auto',
       audioLevelDisplayMode: 'auto',
       backgroundImageURI: null
     });
 
         /// Private Methods
     var logAnalyticsEvent = function(action, variation, payloadType, payload) {
           _analytics.logEvent({
             action: action,
@@ -17838,19 +17756,17 @@ waitForDomReady();
           cleanupLocalStream();
           _webRTCStream = webOTStream;
 
           _microphone = new OT.Microphone(_webRTCStream, !_publishProperties.publishAudio);
           this.publishVideo(_publishProperties.publishVideo &&
             _webRTCStream.getVideoTracks().length > 0);
 
           this.accessAllowed = true;
-          this.dispatchEvent(
-            new OT.Event(OT.Event.names.ACCESS_ALLOWED, false)
-          );
+          this.dispatchEvent(new OT.Event(OT.Event.names.ACCESS_ALLOWED, false));
 
           var videoContainerOptions = {
             muted: true,
             error: OT.$.bind(onVideoError, this)
           };
 
           _targetElement = _container.bindVideo(_webRTCStream,
                                             videoContainerOptions,
@@ -17858,17 +17774,17 @@ waitForDomReady();
             if (err) {
               onLoadFailure.call(this, err);
               return;
             }
 
             onLoaded.call(this);
           }, this));
 
-          if(_audioLevelSampler) {
+          if(_audioLevelSampler && webOTStream.getAudioTracks().length > 0) {
             _audioLevelSampler.webOTStream = webOTStream;
           }
 
         },
 
         onStreamAvailableError = function(error) {
           OT.error('OT.Publisher.onStreamAvailableError ' + error.name + ': ' + error.message);
 
@@ -17896,115 +17812,34 @@ waitForDomReady();
           _state.set('Failed');
           this.trigger('publishComplete', new OT.Error(OT.ExceptionCodes.UNABLE_TO_PUBLISH,
               'Publisher Access Denied: Permission Denied' +
                   (error.message ? ': ' + error.message : '')));
 
           logAnalyticsEvent('publish', 'Failure', 'reason',
             'GetUserMedia:Publisher Access Denied: Permission Denied');
 
-          var browser = OT.$.browserVersion();
-
-          var event = new OT.Event(OT.Event.names.ACCESS_DENIED),
-            defaultAction = function() {
-              if(!event.isDefaultPrevented()) {
-                if(browser.browser === 'Chrome') {
-                  if (_container) {
-                    _container.addError('', null, 'OT_publisher-denied-chrome');
-                  }
-                  if(!accessDialogWasOpened) {
-                    OT.Dialogs.AllowDeny.Chrome.previouslyDenied(window.location.hostname);
-                  } else {
-                    OT.Dialogs.AllowDeny.Chrome.deniedNow();
-                  }
-                } else if(browser.browser === 'Firefox') {
-                  if(_container) {
-                    _container.addError('', 'Click the reload button in the URL bar to change ' +
-                      'camera & mic settings.', 'OT_publisher-denied-firefox');
-                  }
-                  OT.Dialogs.AllowDeny.Firefox.denied().on({
-                    refresh: function() {
-                      window.location.reload();
-                    }
-                  });
-                }
-              }
-            };
-
-          this.dispatchEvent(event, defaultAction);
-        },
-
-        accessDialogPrompt,
-        accessDialogChromeTimeout,
-        accessDialogFirefoxTimeout,
+          this.dispatchEvent(new OT.Event(OT.Event.names.ACCESS_DENIED));
+        },
+
         accessDialogWasOpened = false,
 
         onAccessDialogOpened = function() {
 
           accessDialogWasOpened = true;
 
           logAnalyticsEvent('accessDialog', 'Opened', '', '');
 
-          var browser = OT.$.browserVersion();
-
-          this.dispatchEvent(
-            new OT.Event(OT.Event.names.ACCESS_DIALOG_OPENED, true),
-            function(event) {
-              if(!event.isDefaultPrevented()) {
-                if(browser.browser === 'Chrome') {
-                  accessDialogChromeTimeout = setTimeout(function() {
-                    accessDialogChromeTimeout = null;
-                    logAnalyticsEvent('allowDenyHelpers', 'show', 'version', 'Chrome');
-                    accessDialogPrompt = OT.Dialogs.AllowDeny.Chrome.initialPrompt();
-                    accessDialogPrompt.on('closeButtonClicked', function() {
-                      logAnalyticsEvent('allowDenyHelpers', 'dismissed', 'version', 'Chrome');
-                    });
-                  }, 5000);
-                } else if(browser.browser === 'Firefox') {
-                  accessDialogFirefoxTimeout = setTimeout(function() {
-                    accessDialogFirefoxTimeout = null;
-                    logAnalyticsEvent('allowDenyHelpers', 'show', 'version', 'Firefox');
-                    accessDialogPrompt = OT.Dialogs.AllowDeny.Firefox.maybeDenied();
-                    accessDialogPrompt.on('closeButtonClicked', function() {
-                      logAnalyticsEvent('allowDenyHelpers', 'dismissed', 'version', 'Firefox');
-                    });
-                  }, 7000);
-                }
-              } else {
-                logAnalyticsEvent('allowDenyHelpers', 'developerPrevented', '', '');
-              }
-            }
-          );
+          this.dispatchEvent(new OT.Event(OT.Event.names.ACCESS_DIALOG_OPENED, true));
         },
 
         onAccessDialogClosed = function() {
           logAnalyticsEvent('accessDialog', 'Closed', '', '');
 
-          if(accessDialogChromeTimeout) {
-            clearTimeout(accessDialogChromeTimeout);
-            logAnalyticsEvent('allowDenyHelpers', 'notShown', 'version', 'Chrome');
-            accessDialogChromeTimeout = null;
-          }
-
-          if(accessDialogFirefoxTimeout) {
-            clearTimeout(accessDialogFirefoxTimeout);
-            logAnalyticsEvent('allowDenyHelpers', 'notShown', 'version', 'Firefox');
-            accessDialogFirefoxTimeout = null;
-          }
-
-          if(accessDialogPrompt) {
-            accessDialogPrompt.close();
-            var browser = OT.$.browserVersion();
-            logAnalyticsEvent('allowDenyHelpers', 'closed', 'version', browser.browser);
-            accessDialogPrompt = null;
-          }
-
-          this.dispatchEvent(
-            new OT.Event(OT.Event.names.ACCESS_DIALOG_CLOSED, false)
-          );
+          this.dispatchEvent( new OT.Event(OT.Event.names.ACCESS_DIALOG_CLOSED, false));
         },
 
         onVideoError = function(errorCode, errorReason) {
           OT.error('OT.Publisher.onVideoError');
 
           var message = errorReason + (errorCode ? ' (' + errorCode + ')' : '');
           logAnalyticsEvent('stream', null, 'reason',
             'Publisher while playing stream: ' + message);
@@ -18155,79 +17990,79 @@ waitForDomReady();
               _chrome.muteButton.setDisplayMode(value);
               _chrome.backingBar.setMuteMode(value);
               break;
 
             case 'audioLevelDisplayMode':
               _chrome.audioLevel.setDisplayMode(value);
               break;
 
-            case 'bugDisplayMode':
-              // bugDisplayMode can't be updated but is used by some partners
-
             case 'backgroundImageURI':
               _container.setBackgroundImageURI(value);
           }
         },
 
         _createChrome = function() {
 
-          if(this.getStyle('bugDisplayMode') === 'off') {
-            logAnalyticsEvent('bugDisplayMode', 'createChrome', 'mode', 'off');
-          }
           if(!this.getStyle('showArchiveStatus')) {
             logAnalyticsEvent('showArchiveStatus', 'createChrome', 'mode', 'off');
           }
 
           var widgets = {
             backingBar: new OT.Chrome.BackingBar({
               nameMode: !_publishProperties.name ? 'off' : this.getStyle('nameDisplayMode'),
               muteMode: chromeButtonMode.call(this, this.getStyle('buttonDisplayMode'))
             }),
 
             name: new OT.Chrome.NamePanel({
               name: _publishProperties.name,
-              mode: this.getStyle('nameDisplayMode'),
-              bugMode: this.getStyle('bugDisplayMode')
+              mode: this.getStyle('nameDisplayMode')
             }),
 
             muteButton: new OT.Chrome.MuteButton({
               muted: _publishProperties.publishAudio === false,
               mode: chromeButtonMode.call(this, this.getStyle('buttonDisplayMode'))
             }),
 
-            opentokButton: new OT.Chrome.OpenTokButton({
-              mode: this.getStyle('bugDisplayMode')
-            }),
-
             archive: new OT.Chrome.Archiving({
               show: this.getStyle('showArchiveStatus'),
               archiving: false
             })
           };
 
-          if(_audioLevelCapable) {
+          if (_audioLevelCapable) {
+            var audioLevelTransformer = new OT.AudioLevelTransformer();
+
+            var audioLevelUpdatedHandler = function(evt) {
+              _audioLevelMeter.setValue(audioLevelTransformer.transform(evt.audioLevel));
+            };
+
             _audioLevelMeter = new OT.Chrome.AudioLevelMeter({
-              mode: this.getStyle('audioLevelDisplayMode')
-            });
-
-            var audioLevelTransformer = new OT.AudioLevelTransformer();
-            this.on('audioLevelUpdated', function(evt) {
-              _audioLevelMeter.setValue(audioLevelTransformer.transform(evt.audioLevel));
+              mode: this.getStyle('audioLevelDisplayMode'),
+              onActivate: function() {
+                _publisher.on('audioLevelUpdated', audioLevelUpdatedHandler);
+              },
+              onPassivate: function() {
+                _publisher.off('audioLevelUpdated', audioLevelUpdatedHandler);
+              }
             });
 
             widgets.audioLevel = _audioLevelMeter;
           }
 
           _chrome = new OT.Chrome({
             parent: _container.domElement
           }).set(widgets).on({
             muted: OT.$.bind(this.publishAudio, this, false),
             unmuted: OT.$.bind(this.publishAudio, this, true)
           });
+
+          if(_audioLevelMeter && this.getStyle('audioLevelDisplayMode') === 'auto') {
+            _audioLevelMeter[_container.audioOnly() ? 'show' : 'hide']();
+          }
         },
 
         reset = OT.$.bind(function() {
           if (_chrome) {
             _chrome.destroy();
             _chrome = null;
           }
 
@@ -18255,16 +18090,27 @@ waitForDomReady();
           this.stream = _stream = null;
           _loaded = false;
 
           this.session = _session = null;
 
           if (!_state.isDestroyed()) _state.set('NotPublishing');
         }, this);
 
+    var setAudioOnly = function(audioOnly) {
+      if (_container) {
+        _container.audioOnly(audioOnly);
+        _container.showPoster(audioOnly);
+      }
+
+      if (_audioLevelMeter && _publisher.getStyle('audioLevelDisplayMode') === 'auto') {
+        _audioLevelMeter[audioOnly ? 'show' : 'hide']();
+      }
+    };
+
     this.publish = function(targetElement, properties) {
       OT.debug('OT.Publisher: publish');
 
       if ( _state.isAttemptingToPublish() || _state.isPublishing() ) reset();
       _state.set('GetUserMedia');
 
       _publishProperties = OT.$.defaults(properties || {}, {
         publishAudio : true,
@@ -18452,18 +18298,17 @@ waitForDomReady();
 
       if (_session && _stream) {
         _stream.setChannelActiveState('audio', value);
       }
 
       return this;
     };
 
-
- /**
+    /**
   * Starts publishing video (if it is currently not being published)
   * when the <code>value</code> is <code>true</code>; stops publishing video
   * (if it is currently being published) when the <code>value</code> is <code>false</code>.
   *
   * @param {Boolean} value Whether to start publishing video (<code>true</code>)
   * or not (<code>false</code>).
   *
   * @see <a href="OT.html#initPublisher">OT.initPublisher()</a>
@@ -18485,20 +18330,17 @@ waitForDomReady();
       // the value of publishVideo at this point. This will be tidied up shortly.
       if (_webRTCStream) {
         var videoTracks = _webRTCStream.getVideoTracks();
         for (var i=0, num=videoTracks.length; i<num; ++i) {
           videoTracks[i].setEnabled(value);
         }
       }
 
-      if(_container) {
-        _container.audioOnly(!value);
-        _container.showPoster(!value);
-      }
+      setAudioOnly(!value);
 
       return this;
     };
 
 
     /**
     * Deletes the Publisher object and removes it from the HTML DOM.
     * <p>
@@ -18933,17 +18775,18 @@ waitForDomReady();
         _audioVolume = 100,
         _state,
         _prevStats,
         _lastSubscribeToVideoReason,
         _audioLevelCapable =  OT.$.hasCapabilities('audioOutputLevelStat') ||
                               OT.$.hasCapabilities('webAudioCapableRemoteStream'),
         _audioLevelSampler,
         _audioLevelRunner,
-        _frameRateRestricted = false;
+        _frameRateRestricted = false,
+        _subscriber = this;
 
     this.id = _domId;
     this.widgetId = _widgetId;
     this.session = _session;
 
     _prevStats = {
       timeStamp: OT.$.now()
     };
@@ -18976,18 +18819,17 @@ waitForDomReady();
 
     OT.StylableComponent(this, {
       nameDisplayMode: 'auto',
       buttonDisplayMode: 'auto',
       audioLevelDisplayMode: 'auto',
       videoDisabledIndicatorDisplayMode: 'auto',
       backgroundImageURI: null,
       showArchiveStatus: true,
-      showMicButton: true,
-      bugDisplayMode: 'auto'
+      showMicButton: true
     });
 
     var logAnalyticsEvent = function(action, variation, payloadType, payload) {
           /* jshint camelcase:false*/
           _analytics.logEvent({
             action: action,
             variation: variation,
             payload_type: payloadType,
@@ -19156,17 +18998,18 @@ waitForDomReady();
               width: _stream.videoDimensions.width,
               height: _stream.videoDimensions.height,
               videoOrientation: _stream.videoDimensions.orientation
             });
 
             onLoaded.call(this, null);
           }, this));
 
-          if (OT.$.hasCapabilities('webAudioCapableRemoteStream') && _audioLevelSampler) {
+          if (OT.$.hasCapabilities('webAudioCapableRemoteStream') && _audioLevelSampler &&
+            webOTStream.getAudioTracks().length > 0) {
             _audioLevelSampler.webOTStream = webOTStream;
           }
 
           logAnalyticsEvent('createPeerConnection', 'StreamAdded', '', '');
           this.trigger('streamAdded', this);
         },
 
         onRemoteStreamRemoved = function(webOTStream) {
@@ -19184,36 +19027,38 @@ waitForDomReady();
         streamDestroyed = function () {
           this.disconnect();
         },
 
         streamUpdated = function(event) {
 
           switch(event.changedProperty) {
             case 'videoDimensions':
+              if (!_streamContainer) {
+                // Ignore videoEmension updates before streamContainer is created OPENTOK-17253
+                break;
+              }
               _streamContainer.orientation({
                 width: event.newValue.width,
                 height: event.newValue.height,
                 videoOrientation: event.newValue.orientation
               });
               break;
 
             case 'videoDisableWarning':
               _chrome.videoDisabledIndicator.setWarning(event.newValue);
               this.dispatchEvent(new OT.VideoDisableWarningEvent(
                 event.newValue ? 'videoDisableWarning' : 'videoDisableWarningLifted'
               ));
               break;
 
             case 'hasVideo':
-              if(_container) {
-                var audioOnly = !(_stream.hasVideo && _properties.subscribeToVideo);
-                _container.audioOnly(audioOnly);
-                _container.showPoster(audioOnly);
-              }
+
+              setAudioOnly(!(_stream.hasVideo && _properties.subscribeToVideo));
+
               this.dispatchEvent(new OT.VideoEnabledChangedEvent(
                 _stream.hasVideo ? 'videoEnabled' : 'videoDisabled', {
                 reason: 'publishVideo'
               }));
               break;
 
             case 'hasAudio':
               // noop
@@ -19258,65 +19103,60 @@ waitForDomReady();
               _chrome.muteButton.setDisplayMode(value);
               _chrome.backingBar.setMuteMode(value);
               break;
 
             case 'audioLevelDisplayMode':
               _chrome.audioLevel.setDisplayMode(value);
               break;
 
-            case 'bugDisplayMode':
-              // bugDisplayMode can't be updated but is used by some partners
-
             case 'backgroundImageURI':
               _container.setBackgroundImageURI(value);
           }
         },
 
         _createChrome = function() {
-          
-          if(this.getStyle('bugDisplayMode') === 'off') {
-            logAnalyticsEvent('bugDisplayMode', 'createChrome', 'mode', 'off');
-          }
 
           var widgets = {
             backingBar: new OT.Chrome.BackingBar({
               nameMode: !_properties.name ? 'off' : this.getStyle('nameDisplayMode'),
               muteMode: chromeButtonMode.call(this, this.getStyle('showMuteButton'))
             }),
 
             name: new OT.Chrome.NamePanel({
               name: _properties.name,
-              mode: this.getStyle('nameDisplayMode'),
-              bugMode: this.getStyle('bugDisplayMode')
+              mode: this.getStyle('nameDisplayMode')
             }),
 
             muteButton: new OT.Chrome.MuteButton({
               muted: _properties.muted,
               mode: chromeButtonMode.call(this, this.getStyle('showMuteButton'))
             }),
 
-            opentokButton: new OT.Chrome.OpenTokButton({
-              mode: this.getStyle('bugDisplayMode')
-            }),
-
             archive: new OT.Chrome.Archiving({
               show: this.getStyle('showArchiveStatus'),
               archiving: false
             })
           };
 
-          if(_audioLevelCapable) {
+          if (_audioLevelCapable) {
+            var audioLevelTransformer = new OT.AudioLevelTransformer();
+
+            var audioLevelUpdatedHandler = function(evt) {
+              _audioLevelMeter.setValue(audioLevelTransformer.transform(evt.audioLevel));
+            };
+
             _audioLevelMeter = new OT.Chrome.AudioLevelMeter({
-              mode: this.getStyle('audioLevelDisplayMode')
-            });
-
-            var audioLevelTransformer = new OT.AudioLevelTransformer();
-            this.on('audioLevelUpdated', function(evt) {
-              _audioLevelMeter.setValue(audioLevelTransformer.transform(evt.audioLevel));
+              mode: this.getStyle('audioLevelDisplayMode'),
+              onActivate: function() {
+                _subscriber.on('audioLevelUpdated', audioLevelUpdatedHandler);
+              },
+              onPassivate: function() {
+                _subscriber.off('audioLevelUpdated', audioLevelUpdatedHandler);
+              }
             });
 
             widgets.audioLevel = _audioLevelMeter;
           }
 
           widgets.videoDisabledIndicator = new OT.Chrome.VideoDisabledIndicator({
             mode: this.getStyle('videoDisabledDisplayMode')
           });
@@ -19327,29 +19167,43 @@ waitForDomReady();
             muted: function() {
               muteAudio.call(this, true);
             },
 
             unmuted: function() {
               muteAudio.call(this, false);
             }
           }, this);
+
+          if(_audioLevelMeter && this.getStyle('audioLevelDisplayMode') === 'auto') {
+            _audioLevelMeter[_container.audioOnly() ? 'show' : 'hide']();
+          }
         },
 
         _showError = function() {
           // Display the error message inside the container, assuming it's
           // been created by now.
           if (_container) {
             _container.addError(
               'The stream was unable to connect due to a network error.',
               'Make sure your connection isn\'t blocked by a firewall.'
             );
           }
         };
 
+    var setAudioOnly = function(audioOnly) {
+      if(_container) {
+        _container.audioOnly(audioOnly);
+        _container.showPoster(audioOnly);
+      }
+
+      if (_audioLevelMeter && _subscriber.getStyle('audioLevelDisplayMode') === 'auto') {
+        _audioLevelMeter[audioOnly ? 'show' : 'hide']();
+      }
+    };
 
     this.subscribe = function(stream) {
       OT.debug('OT.Subscriber: subscribe to ' + stream.id);
 
       if (_state.isSubscribing()) {
         // @todo error
         OT.error('OT.Subscriber.Subscribe: Cannot subscribe, already subscribing.');
         return false;
@@ -19413,28 +19267,27 @@ waitForDomReady();
         }, this);
 
         // initialize the peer connection AFTER we've added the event listeners
         _peerConnection.init();
 
         if (OT.$.hasCapabilities('audioOutputLevelStat')) {
           _audioLevelSampler = new OT.GetStatsAudioLevelSampler(_peerConnection, 'out');
         } else if (OT.$.hasCapabilities('webAudioCapableRemoteStream')) {
-          _audioLevelSampler = new OT.AnalyserAudioLevelSampler(new window.AudioContext());
+          _audioLevelSampler = new OT.AnalyserAudioLevelSampler(OT.audioContext());
         }
 
         if(_audioLevelSampler) {
-          var subscriber = this;
           // sample with interval to minimise disturbance on animation loop but dispatch the
           // event with RAF since the main purpose is animation of a meter
           _audioLevelRunner = new OT.IntervalRunner(function() {
             _audioLevelSampler.sample(function(audioOutputLevel) {
               if (audioOutputLevel !== null) {
                 OT.$.requestAnimationFrame(function() {
-                  subscriber.dispatchEvent(
+                  _subscriber.dispatchEvent(
                     new OT.AudioLevelUpdatedEvent(audioOutputLevel));
                 });
               }
             });
           }, 60);
         }
       } else {
         logAnalyticsEvent('createPeerConnection', 'Attempt', '', '');
@@ -19744,26 +19597,23 @@ waitForDomReady();
     * @see <a href="StreamPropertyChangedEvent.html">StreamPropertyChangedEvent</a>
     *
     * @method #subscribeToVideo
     * @memberOf Subscriber
     */
     this.subscribeToVideo = function(pValue, reason) {
       var value = OT.$.castToBoolean(pValue, true);
 
-      if(_container) {
-        var audioOnly = !(value && _stream.hasVideo);
-        _container.audioOnly(audioOnly);
-        _container.showPoster(audioOnly);
-        if(value && _container.video()) {
-          _container.loading(value);
-          _container.video().whenTimeIncrements(function(){
-            _container.loading(false);
-          }, this);
-        }
+      setAudioOnly(!(value && _stream.hasVideo));
+
+      if ( value && _container  && _container.video()) {
+        _container.loading(value);
+        _container.video().whenTimeIncrements(function() {
+          _container.loading(false);
+        }, this);
       }
 
       if (_chrome && _chrome.videoDisabledIndicator) {
         _chrome.videoDisabledIndicator.disableVideo(false);
       }
 
       if (_peerConnection) {
         _peerConnection.subscribeToVideo(value);
--- a/browser/components/loop/standalone/content/js/standaloneRoomViews.js
+++ b/browser/components/loop/standalone/content/js/standaloneRoomViews.js
@@ -244,17 +244,16 @@ loop.standaloneRoomViews = (function(moz
       // @see https://bugzilla.mozilla.org/show_bug.cgi?id=1020445
       return {
         insertMode: "append",
         width: "100%",
         height: "100%",
         publishVideo: true,
         style: {
           audioLevelDisplayMode: "off",
-          bugDisplayMode: "off",
           buttonDisplayMode: "off",
           nameDisplayMode: "off",
           videoDisabledDisplayMode: "off"
         }
       };
     },
 
     /**
--- a/browser/components/loop/standalone/content/js/standaloneRoomViews.jsx
+++ b/browser/components/loop/standalone/content/js/standaloneRoomViews.jsx
@@ -244,17 +244,16 @@ loop.standaloneRoomViews = (function(moz
       // @see https://bugzilla.mozilla.org/show_bug.cgi?id=1020445
       return {
         insertMode: "append",
         width: "100%",
         height: "100%",
         publishVideo: true,
         style: {
           audioLevelDisplayMode: "off",
-          bugDisplayMode: "off",
           buttonDisplayMode: "off",
           nameDisplayMode: "off",
           videoDisabledDisplayMode: "off"
         }
       };
     },
 
     /**