diff --git a/CHANGES b/CHANGES
index fd5f1f2..4f46f28 100644
--- a/CHANGES
+++ b/CHANGES
@@ -2,7 +2,9 @@ Galene 0.9.1 (unreleased)
 
   * Added an example client with minimal functionality under static/example.
   * Fixed a race condition that would cause the client to run with
-    undefined settings if the initial connection was extremely quick.
+    undefined settings if the initial connection was extremely fast.
+  * Changed the token login to force a user interaction in order to avoid
+    issues with autoplay.
   * Implemented client-side timeouts (we already had one on the server side).
   * Reduced the server-side timeout.
   * Don't attempt to set the file descriptor limit, since recent versions
diff --git a/static/galene.css b/static/galene.css
index c2cec85..cb41c3c 100644
--- a/static/galene.css
+++ b/static/galene.css
@@ -715,17 +715,17 @@ h1 {
     font-weight: bold;
 }
 
-.userform {
+.login {
     display: inline
 }
 
-.userform label {
+.loginform label {
     min-width: 3em;
     display: inline-block;
     padding-top: 10px;
 }
 
-.userform input[type="text"], .userform input[type="password"] {
+.loginform input[type="text"], .loginform input[type="password"] {
     width: 100%;
 }
 
diff --git a/static/galene.html b/static/galene.html
index 0e7f922..556eeec 100644
--- a/static/galene.html
+++ b/static/galene.html
@@ -101,10 +101,12 @@
               </div>
               <div class="login-container invisible" id="login-container">
                 <div class="login-box">
-                  <form id="userform" class="userform">
-                    <label for="username">Username</label>
-                    <input id="username" type="text" name="username"
-                           autocomplete="username" class="form-control"/>
+                  <form id="loginform" class="loginform">
+                    <div id="userform">
+                      <label for="username">Username</label>
+                      <input id="username" type="text" name="username"
+                             autocomplete="username" class="form-control"/>
+                    </div>
                     <div id="passwordform">
                       <label for="password">Password</label>
                       <input id="password" type="password" name="password"
diff --git a/static/galene.js b/static/galene.js
index 4c5dc16..7c21c40 100644
--- a/static/galene.js
+++ b/static/galene.js
@@ -35,8 +35,8 @@ let pwAuth = false;
 /** @type {string} */
 let token = null;
 
-/** @type {boolean} */
-let connectingAgain = false;
+/** @type {string} */
+let probingState = null;
 
 /**
  * @typedef {Object} settings
@@ -317,8 +317,6 @@ function setConnected(connected) {
     } else {
         userbox.classList.add('invisible');
         connectionbox.classList.remove('invisible');
-        if(!connectingAgain)
-            displayError('Disconnected', 'error');
         hideVideo();
         window.onresize = null;
     }
@@ -329,9 +327,7 @@ function setConnected(connected) {
  */
 async function gotConnected() {
     setConnected(true);
-    let again = connectingAgain;
-    connectingAgain = false;
-    await join(again);
+    await join();
 }
 
 /**
@@ -352,10 +348,7 @@ function setChangePassword(username) {
     }
 }
 
-/**
- * @param {boolean} again
- */
-async function join(again) {
+async function join() {
     let username = getInputElement('username').value.trim();
     let credentials;
     if(token) {
@@ -364,11 +357,30 @@ async function join(again) {
             type: 'token',
             token: token,
         };
-        if(!again)
-            // the first time around, we need to join with no username in
-            // order to give the server a chance to reply with 'need-username'.
+        switch(probingState) {
+        case null:
+            // when logging in with a token, we need to give the user
+            // a chance to interact with the page in order to enable
+            // autoplay.  Probe the group first in order to determine if
+            // we need a username.  We should really extend the protocol
+            // to have a simpler protocol for probing.
+            probingState = 'probing';
             username = null;
+            break;
+        case 'need-username':
+        case 'success':
+            probingState = null;
+            break
+        default:
+            console.warn(`Unexpected probing state ${probingState}`);
+            probingState = null;
+            break;
+        }
     } else {
+        if(probingState !== null) {
+            console.warn(`Unexpected probing state ${probingState}`);
+            probingState = null;
+        }
         let pw = getInputElement('password').value;
         getInputElement('password').value = '';
         if(!groupStatus.authServer) {
@@ -420,9 +432,9 @@ function gotClose(code, reason) {
     if(code != 1000) {
         console.warn('Socket close', code, reason);
     }
-    let form = document.getElementById('userform');
+    let form = document.getElementById('loginform');
     if(!(form instanceof HTMLFormElement))
-        throw new Error('Bad type for userform');
+        throw new Error('Bad type for loginform');
     form.active = true;
 }
 
@@ -2471,14 +2483,13 @@ async function gotJoined(kind, group, perms, status, data, error, message) {
 
     switch(kind) {
     case 'fail':
-        if(error === 'need-username' || error === 'duplicate-username') {
+        if(probingState === 'probing' && error === 'need-username') {
+            probingState = 'need-username';
             setVisibility('passwordform', false);
-            connectingAgain = true;
         } else {
             token = null;
-        }
-        if(error !== 'need-username')
             displayError('The server said: ' + message);
+        }
         this.close();
         setButtonsVisibility();
         return;
@@ -2489,13 +2500,21 @@ async function gotJoined(kind, group, perms, status, data, error, message) {
         return;
     case 'leave':
         this.close();
-        token = null;
         setButtonsVisibility();
         setChangePassword(null);
         return;
     case 'join':
     case 'change':
-        token = null;
+        if(probingState === 'probing') {
+            probingState = 'success';
+            setVisibility('userform', false);
+            setVisibility('passwordform', false);
+            this.close();
+            setButtonsVisibility();
+            return;
+        } else {
+            token = null;
+        }
         // don't discard endPoint and friends
         for(let key in status)
             groupStatus[key] = status[key];
@@ -2515,8 +2534,6 @@ async function gotJoined(kind, group, perms, status, data, error, message) {
         return;
     }
 
-    token = null;
-
     let input = /** @type{HTMLTextAreaElement} */
         (document.getElementById('input'));
     input.placeholder = 'Type /help for help';
@@ -3907,12 +3924,12 @@ function displayMessage(message) {
     return displayError(message, "info");
 }
 
-document.getElementById('userform').onsubmit = async function(e) {
+document.getElementById('loginform').onsubmit = async function(e) {
     e.preventDefault();
 
     let form = this;
     if(!(form instanceof HTMLFormElement))
-        throw new Error('Bad type for userform');
+        throw new Error('Bad type for loginform');
 
     setVisibility('passwordform', true);
 
