Moving to matrix-js-sdk
This commit is contained in:
		
					parent
					
						
							
								4d7e5583eb
							
						
					
				
			
			
				commit
				
					
						d813509541
					
				
			
		
					 13 changed files with 821 additions and 2519 deletions
				
			
		| 
						 | 
					@ -8,7 +8,6 @@
 | 
				
			||||||
    <script>
 | 
					    <script>
 | 
				
			||||||
      window.global = window;
 | 
					      window.global = window;
 | 
				
			||||||
    </script>
 | 
					    </script>
 | 
				
			||||||
    <script src="/browser-matrix.js"></script>
 | 
					 | 
				
			||||||
  </head>
 | 
					  </head>
 | 
				
			||||||
  <body>
 | 
					  <body>
 | 
				
			||||||
    <div id="root"></div>
 | 
					    <div id="root"></div>
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										1110
									
								
								package-lock.json
									
										
									
										generated
									
									
									
								
							
							
						
						
									
										1110
									
								
								package-lock.json
									
										
									
										generated
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load diff
											
										
									
								
							| 
						 | 
					@ -11,7 +11,7 @@
 | 
				
			||||||
    "color-hash": "^2.0.1",
 | 
					    "color-hash": "^2.0.1",
 | 
				
			||||||
    "events": "^3.3.0",
 | 
					    "events": "^3.3.0",
 | 
				
			||||||
    "lodash-move": "^1.1.1",
 | 
					    "lodash-move": "^1.1.1",
 | 
				
			||||||
    "matrix-js-sdk": "^12.0.1",
 | 
					    "matrix-js-sdk": "file:../matrix-js-sdk",
 | 
				
			||||||
    "re-resizable": "^6.9.0",
 | 
					    "re-resizable": "^6.9.0",
 | 
				
			||||||
    "react": "^17.0.0",
 | 
					    "react": "^17.0.0",
 | 
				
			||||||
    "react-dom": "^17.0.0",
 | 
					    "react-dom": "^17.0.0",
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										23
									
								
								src/App.jsx
									
										
									
									
									
								
							
							
						
						
									
										23
									
								
								src/App.jsx
									
										
									
									
									
								
							| 
						 | 
					@ -22,13 +22,14 @@ import {
 | 
				
			||||||
  Redirect,
 | 
					  Redirect,
 | 
				
			||||||
  useLocation,
 | 
					  useLocation,
 | 
				
			||||||
} from "react-router-dom";
 | 
					} from "react-router-dom";
 | 
				
			||||||
import { useConferenceCallManager } from "./ConferenceCallManagerHooks";
 | 
					import { useClient } from "./ConferenceCallManagerHooks";
 | 
				
			||||||
import { Home } from "./Home";
 | 
					import { Home } from "./Home";
 | 
				
			||||||
import { Room, RoomAuth } from "./Room";
 | 
					import { Room } from "./Room";
 | 
				
			||||||
import { GridDemo } from "./GridDemo";
 | 
					import { GridDemo } from "./GridDemo";
 | 
				
			||||||
import { RegisterPage } from "./RegisterPage";
 | 
					import { RegisterPage } from "./RegisterPage";
 | 
				
			||||||
import { LoginPage } from "./LoginPage";
 | 
					import { LoginPage } from "./LoginPage";
 | 
				
			||||||
import { Center } from "./Layout";
 | 
					import { Center } from "./Layout";
 | 
				
			||||||
 | 
					import { GuestAuthPage } from "./GuestAuthPage";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export default function App() {
 | 
					export default function App() {
 | 
				
			||||||
  const { protocol, host } = window.location;
 | 
					  const { protocol, host } = window.location;
 | 
				
			||||||
| 
						 | 
					@ -37,12 +38,12 @@ export default function App() {
 | 
				
			||||||
  const {
 | 
					  const {
 | 
				
			||||||
    loading,
 | 
					    loading,
 | 
				
			||||||
    authenticated,
 | 
					    authenticated,
 | 
				
			||||||
    error,
 | 
					    client,
 | 
				
			||||||
    manager,
 | 
					 | 
				
			||||||
    login,
 | 
					    login,
 | 
				
			||||||
    loginAsGuest,
 | 
					    logout,
 | 
				
			||||||
 | 
					    registerGuest,
 | 
				
			||||||
    register,
 | 
					    register,
 | 
				
			||||||
  } = useConferenceCallManager(homeserverUrl);
 | 
					  } = useClient(homeserverUrl);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  return (
 | 
					  return (
 | 
				
			||||||
    <Router>
 | 
					    <Router>
 | 
				
			||||||
| 
						 | 
					@ -54,19 +55,19 @@ export default function App() {
 | 
				
			||||||
        ) : (
 | 
					        ) : (
 | 
				
			||||||
          <Switch>
 | 
					          <Switch>
 | 
				
			||||||
            <AuthenticatedRoute authenticated={authenticated} exact path="/">
 | 
					            <AuthenticatedRoute authenticated={authenticated} exact path="/">
 | 
				
			||||||
              <Home manager={manager} error={error} />
 | 
					              <Home client={client} onLogout={logout} />
 | 
				
			||||||
            </AuthenticatedRoute>
 | 
					            </AuthenticatedRoute>
 | 
				
			||||||
            <Route exact path="/login">
 | 
					            <Route exact path="/login">
 | 
				
			||||||
              <LoginPage onLogin={login} error={error} />
 | 
					              <LoginPage onLogin={login} />
 | 
				
			||||||
            </Route>
 | 
					            </Route>
 | 
				
			||||||
            <Route exact path="/register">
 | 
					            <Route exact path="/register">
 | 
				
			||||||
              <RegisterPage onRegister={register} error={error} />
 | 
					              <RegisterPage onRegister={register} />
 | 
				
			||||||
            </Route>
 | 
					            </Route>
 | 
				
			||||||
            <Route path="/room/:roomId">
 | 
					            <Route path="/room/:roomId">
 | 
				
			||||||
              {authenticated ? (
 | 
					              {authenticated ? (
 | 
				
			||||||
                <Room manager={manager} error={error} />
 | 
					                <Room client={client} />
 | 
				
			||||||
              ) : (
 | 
					              ) : (
 | 
				
			||||||
                <RoomAuth error={error} onLoginAsGuest={loginAsGuest} />
 | 
					                <GuestAuthPage onRegisterGuest={registerGuest} />
 | 
				
			||||||
              )}
 | 
					              )}
 | 
				
			||||||
            </Route>
 | 
					            </Route>
 | 
				
			||||||
            <Route exact path="/grid">
 | 
					            <Route exact path="/grid">
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,981 +0,0 @@
 | 
				
			||||||
/*
 | 
					 | 
				
			||||||
Copyright 2021 New Vector Ltd
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
Licensed under the Apache License, Version 2.0 (the "License");
 | 
					 | 
				
			||||||
you may not use this file except in compliance with the License.
 | 
					 | 
				
			||||||
You may obtain a copy of the License at
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    http://www.apache.org/licenses/LICENSE-2.0
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
Unless required by applicable law or agreed to in writing, software
 | 
					 | 
				
			||||||
distributed under the License is distributed on an "AS IS" BASIS,
 | 
					 | 
				
			||||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | 
					 | 
				
			||||||
See the License for the specific language governing permissions and
 | 
					 | 
				
			||||||
limitations under the License.
 | 
					 | 
				
			||||||
*/
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
import EventEmitter from "events";
 | 
					 | 
				
			||||||
import { ConferenceCallDebugger } from "./ConferenceCallDebugger";
 | 
					 | 
				
			||||||
import { randomString } from "./randomstring";
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const CONF_ROOM = "me.robertlong.conf";
 | 
					 | 
				
			||||||
const CONF_PARTICIPANT = "me.robertlong.conf.participant";
 | 
					 | 
				
			||||||
const PARTICIPANT_TIMEOUT = 1000 * 15;
 | 
					 | 
				
			||||||
const SPEAKING_THRESHOLD = -80;
 | 
					 | 
				
			||||||
const ACTIVE_SPEAKER_INTERVAL = 1000;
 | 
					 | 
				
			||||||
const ACTIVE_SPEAKER_SAMPLES = 8;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
function waitForSync(client) {
 | 
					 | 
				
			||||||
  return new Promise((resolve, reject) => {
 | 
					 | 
				
			||||||
    const onSync = (state) => {
 | 
					 | 
				
			||||||
      if (state === "PREPARED") {
 | 
					 | 
				
			||||||
        resolve();
 | 
					 | 
				
			||||||
        client.removeListener("sync", onSync);
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    };
 | 
					 | 
				
			||||||
    client.on("sync", onSync);
 | 
					 | 
				
			||||||
  });
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export class ConferenceCallManager extends EventEmitter {
 | 
					 | 
				
			||||||
  static async restore(homeserverUrl) {
 | 
					 | 
				
			||||||
    try {
 | 
					 | 
				
			||||||
      const authStore = localStorage.getItem("matrix-auth-store");
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      if (authStore) {
 | 
					 | 
				
			||||||
        const { user_id, device_id, access_token } = JSON.parse(authStore);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        const client = matrixcs.createClient({
 | 
					 | 
				
			||||||
          baseUrl: homeserverUrl,
 | 
					 | 
				
			||||||
          accessToken: access_token,
 | 
					 | 
				
			||||||
          userId: user_id,
 | 
					 | 
				
			||||||
          deviceId: device_id,
 | 
					 | 
				
			||||||
        });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        const manager = new ConferenceCallManager(client);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        await client.startClient({
 | 
					 | 
				
			||||||
          // dirty hack to reduce chance of gappy syncs
 | 
					 | 
				
			||||||
          // should be fixed by spotting gaps and backpaginating
 | 
					 | 
				
			||||||
          initialSyncLimit: 50,
 | 
					 | 
				
			||||||
        });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        await waitForSync(client);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        return manager;
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    } catch (err) {
 | 
					 | 
				
			||||||
      localStorage.removeItem("matrix-auth-store");
 | 
					 | 
				
			||||||
      throw err;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  static async login(homeserverUrl, username, password) {
 | 
					 | 
				
			||||||
    try {
 | 
					 | 
				
			||||||
      const registrationClient = matrixcs.createClient(homeserverUrl);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      const { user_id, device_id, access_token } =
 | 
					 | 
				
			||||||
        await registrationClient.loginWithPassword(username, password);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      const client = matrixcs.createClient({
 | 
					 | 
				
			||||||
        baseUrl: homeserverUrl,
 | 
					 | 
				
			||||||
        accessToken: access_token,
 | 
					 | 
				
			||||||
        userId: user_id,
 | 
					 | 
				
			||||||
        deviceId: device_id,
 | 
					 | 
				
			||||||
      });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      localStorage.setItem(
 | 
					 | 
				
			||||||
        "matrix-auth-store",
 | 
					 | 
				
			||||||
        JSON.stringify({ user_id, device_id, access_token })
 | 
					 | 
				
			||||||
      );
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      const manager = new ConferenceCallManager(client);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      await client.startClient({
 | 
					 | 
				
			||||||
        // dirty hack to reduce chance of gappy syncs
 | 
					 | 
				
			||||||
        // should be fixed by spotting gaps and backpaginating
 | 
					 | 
				
			||||||
        initialSyncLimit: 50,
 | 
					 | 
				
			||||||
      });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      await waitForSync(client);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      return manager;
 | 
					 | 
				
			||||||
    } catch (err) {
 | 
					 | 
				
			||||||
      localStorage.removeItem("matrix-auth-store");
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      throw err;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  static async loginAsGuest(homeserverUrl, displayName) {
 | 
					 | 
				
			||||||
    const registrationClient = matrixcs.createClient(homeserverUrl);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    const { user_id, device_id, access_token } =
 | 
					 | 
				
			||||||
      await registrationClient.registerGuest();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    const client = matrixcs.createClient({
 | 
					 | 
				
			||||||
      baseUrl: homeserverUrl,
 | 
					 | 
				
			||||||
      accessToken: access_token,
 | 
					 | 
				
			||||||
      userId: user_id,
 | 
					 | 
				
			||||||
      deviceId: device_id,
 | 
					 | 
				
			||||||
    });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    await client.setDisplayName(displayName);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    client.setGuest(true);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    const manager = new ConferenceCallManager(client);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    await client.startClient({
 | 
					 | 
				
			||||||
      // dirty hack to reduce chance of gappy syncs
 | 
					 | 
				
			||||||
      // should be fixed by spotting gaps and backpaginating
 | 
					 | 
				
			||||||
      initialSyncLimit: 50,
 | 
					 | 
				
			||||||
    });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    await waitForSync(client);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    return manager;
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  static async register(homeserverUrl, username, password) {
 | 
					 | 
				
			||||||
    try {
 | 
					 | 
				
			||||||
      const registrationClient = matrixcs.createClient(homeserverUrl);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      const { user_id, device_id, access_token } =
 | 
					 | 
				
			||||||
        await registrationClient.register(username, password, null, {
 | 
					 | 
				
			||||||
          type: "m.login.dummy",
 | 
					 | 
				
			||||||
        });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      const client = matrixcs.createClient({
 | 
					 | 
				
			||||||
        baseUrl: homeserverUrl,
 | 
					 | 
				
			||||||
        accessToken: access_token,
 | 
					 | 
				
			||||||
        userId: user_id,
 | 
					 | 
				
			||||||
        deviceId: device_id,
 | 
					 | 
				
			||||||
      });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      localStorage.setItem(
 | 
					 | 
				
			||||||
        "matrix-auth-store",
 | 
					 | 
				
			||||||
        JSON.stringify({ user_id, device_id, access_token })
 | 
					 | 
				
			||||||
      );
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      const manager = new ConferenceCallManager(client);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      await client.startClient({
 | 
					 | 
				
			||||||
        // dirty hack to reduce chance of gappy syncs
 | 
					 | 
				
			||||||
        // should be fixed by spotting gaps and backpaginating
 | 
					 | 
				
			||||||
        initialSyncLimit: 50,
 | 
					 | 
				
			||||||
      });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      await waitForSync(client);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      return manager;
 | 
					 | 
				
			||||||
    } catch (err) {
 | 
					 | 
				
			||||||
      localStorage.removeItem("matrix-auth-store");
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      throw err;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  constructor(client) {
 | 
					 | 
				
			||||||
    super();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    this.client = client;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    this.room = null;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    // The session id is used to re-initiate calls if the user's participant
 | 
					 | 
				
			||||||
    // session id has changed
 | 
					 | 
				
			||||||
    this.sessionId = randomString(16);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    this._memberParticipantStateTimeout = null;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    // Whether or not we have entered the conference call.
 | 
					 | 
				
			||||||
    this.entered = false;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    this._left = false;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    // The MatrixCalls that were picked up by the Call.incoming listener,
 | 
					 | 
				
			||||||
    // before the user entered the conference call.
 | 
					 | 
				
			||||||
    this._incomingCallQueue = [];
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    // A representation of the conference call data for each room member
 | 
					 | 
				
			||||||
    // that has entered the call.
 | 
					 | 
				
			||||||
    this.participants = [];
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    this.localVideoStream = null;
 | 
					 | 
				
			||||||
    this.localParticipant = null;
 | 
					 | 
				
			||||||
    this.localCallFeed = null;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    this.audioMuted = false;
 | 
					 | 
				
			||||||
    this.videoMuted = false;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    this.activeSpeaker = null;
 | 
					 | 
				
			||||||
    this._speakerMap = new Map();
 | 
					 | 
				
			||||||
    this._activeSpeakerLoopTimeout = null;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    this.client.on("RoomState.members", this._onRoomStateMembers);
 | 
					 | 
				
			||||||
    this.client.on("Call.incoming", this._onIncomingCall);
 | 
					 | 
				
			||||||
    this.callDebugger = new ConferenceCallDebugger(this);
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  async enter(roomId, timeout = 30000) {
 | 
					 | 
				
			||||||
    this._left = false;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    // Ensure that we have joined the provided room.
 | 
					 | 
				
			||||||
    await this.client.joinRoom(roomId);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    // Get the room info, wait if it hasn't been fetched yet.
 | 
					 | 
				
			||||||
    // Timeout after 30 seconds or the provided duration.
 | 
					 | 
				
			||||||
    const room = await new Promise((resolve, reject) => {
 | 
					 | 
				
			||||||
      const initialRoom = this.client.getRoom(roomId);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      if (initialRoom) {
 | 
					 | 
				
			||||||
        resolve(initialRoom);
 | 
					 | 
				
			||||||
        return;
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      const roomTimeout = setTimeout(() => {
 | 
					 | 
				
			||||||
        reject(new Error("Room could not be found."));
 | 
					 | 
				
			||||||
      }, timeout);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      const roomCallback = (room) => {
 | 
					 | 
				
			||||||
        if (room && room.roomId === roomId) {
 | 
					 | 
				
			||||||
          this.client.removeListener("Room", roomCallback);
 | 
					 | 
				
			||||||
          clearTimeout(roomTimeout);
 | 
					 | 
				
			||||||
          resolve(room);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
      };
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      this.client.on("Room", roomCallback);
 | 
					 | 
				
			||||||
    });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    // Ensure that this room is marked as a conference room so clients can react appropriately
 | 
					 | 
				
			||||||
    const activeConf = room.currentState
 | 
					 | 
				
			||||||
      .getStateEvents(CONF_ROOM, "")
 | 
					 | 
				
			||||||
      ?.getContent()?.active;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    if (!activeConf) {
 | 
					 | 
				
			||||||
      this._sendStateEventWithRetry(
 | 
					 | 
				
			||||||
        room.roomId,
 | 
					 | 
				
			||||||
        CONF_ROOM,
 | 
					 | 
				
			||||||
        { active: true },
 | 
					 | 
				
			||||||
        ""
 | 
					 | 
				
			||||||
      );
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    // Request permissions and get the user's webcam/mic stream if we haven't yet.
 | 
					 | 
				
			||||||
    const userId = this.client.getUserId();
 | 
					 | 
				
			||||||
    const stream = await this.getLocalVideoStream();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    // It's possible to navigate to another page while the microphone permission prompt is
 | 
					 | 
				
			||||||
    // open, so check to see if we've left the call.
 | 
					 | 
				
			||||||
    // Only set class variables below this check so that leaveRoom properly handles
 | 
					 | 
				
			||||||
    // state cleanup.
 | 
					 | 
				
			||||||
    if (this._left) {
 | 
					 | 
				
			||||||
      return;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    this.room = room;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    this.localParticipant = {
 | 
					 | 
				
			||||||
      local: true,
 | 
					 | 
				
			||||||
      userId,
 | 
					 | 
				
			||||||
      displayName: this.client.getUser(this.client.getUserId()).rawDisplayName,
 | 
					 | 
				
			||||||
      sessionId: this.sessionId,
 | 
					 | 
				
			||||||
      call: null,
 | 
					 | 
				
			||||||
      stream,
 | 
					 | 
				
			||||||
      audioMuted: this.audioMuted,
 | 
					 | 
				
			||||||
      videoMuted: this.videoMuted,
 | 
					 | 
				
			||||||
      speaking: false,
 | 
					 | 
				
			||||||
      activeSpeaker: true,
 | 
					 | 
				
			||||||
    };
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    this.activeSpeaker = this.localParticipant;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    this.participants.push(this.localParticipant);
 | 
					 | 
				
			||||||
    this.emit("debugstate", userId, null, "you");
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    this.localCallFeed = new matrixcs.CallFeed(
 | 
					 | 
				
			||||||
      stream,
 | 
					 | 
				
			||||||
      this.localParticipant.userId,
 | 
					 | 
				
			||||||
      "m.usermedia",
 | 
					 | 
				
			||||||
      this.client,
 | 
					 | 
				
			||||||
      this.room.roomId,
 | 
					 | 
				
			||||||
      this.audioMuted,
 | 
					 | 
				
			||||||
      this.videoMuted
 | 
					 | 
				
			||||||
    );
 | 
					 | 
				
			||||||
    this.localCallFeed.on("mute_state_changed", () =>
 | 
					 | 
				
			||||||
      this._onCallFeedMuteStateChanged(
 | 
					 | 
				
			||||||
        this.localParticipant,
 | 
					 | 
				
			||||||
        this.localCallFeed
 | 
					 | 
				
			||||||
      )
 | 
					 | 
				
			||||||
    );
 | 
					 | 
				
			||||||
    this.localCallFeed.setSpeakingThreshold(SPEAKING_THRESHOLD);
 | 
					 | 
				
			||||||
    this.localCallFeed.measureVolumeActivity(true);
 | 
					 | 
				
			||||||
    this.localCallFeed.on("speaking", (speaking) => {
 | 
					 | 
				
			||||||
      this._onCallFeedSpeaking(this.localParticipant, speaking);
 | 
					 | 
				
			||||||
    });
 | 
					 | 
				
			||||||
    this.localCallFeed.on("volume_changed", (maxVolume) =>
 | 
					 | 
				
			||||||
      this._onCallFeedVolumeChange(this.localParticipant, maxVolume)
 | 
					 | 
				
			||||||
    );
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    // Announce to the other room members that we have entered the room.
 | 
					 | 
				
			||||||
    // Continue doing so every PARTICIPANT_TIMEOUT ms
 | 
					 | 
				
			||||||
    this._updateMemberParticipantState();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    this.entered = true;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    // Answer any pending incoming calls.
 | 
					 | 
				
			||||||
    const incomingCallCount = this._incomingCallQueue.length;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    for (let i = 0; i < incomingCallCount; i++) {
 | 
					 | 
				
			||||||
      const call = this._incomingCallQueue.pop();
 | 
					 | 
				
			||||||
      this._onIncomingCall(call);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    // Set up participants for the members currently in the room.
 | 
					 | 
				
			||||||
    // Other members will be picked up by the RoomState.members event.
 | 
					 | 
				
			||||||
    const initialMembers = room.getMembers();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    for (const member of initialMembers) {
 | 
					 | 
				
			||||||
      this._onMemberChanged(member);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    this.emit("entered");
 | 
					 | 
				
			||||||
    this.emit("participants_changed");
 | 
					 | 
				
			||||||
    this._onActiveSpeakerLoop();
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  leaveCall() {
 | 
					 | 
				
			||||||
    if (!this.entered) {
 | 
					 | 
				
			||||||
      return;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    const userId = this.client.getUserId();
 | 
					 | 
				
			||||||
    const currentMemberState = this.room.currentState.getStateEvents(
 | 
					 | 
				
			||||||
      "m.room.member",
 | 
					 | 
				
			||||||
      userId
 | 
					 | 
				
			||||||
    );
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    this._sendStateEventWithRetry(
 | 
					 | 
				
			||||||
      this.room.roomId,
 | 
					 | 
				
			||||||
      "m.room.member",
 | 
					 | 
				
			||||||
      {
 | 
					 | 
				
			||||||
        ...currentMemberState.getContent(),
 | 
					 | 
				
			||||||
        [CONF_PARTICIPANT]: null,
 | 
					 | 
				
			||||||
      },
 | 
					 | 
				
			||||||
      userId
 | 
					 | 
				
			||||||
    );
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    for (const participant of this.participants) {
 | 
					 | 
				
			||||||
      if (!participant.local && participant.call) {
 | 
					 | 
				
			||||||
        participant.call.hangup("user_hangup", false);
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    this.client.stopLocalMediaStream();
 | 
					 | 
				
			||||||
    this.localVideoStream = null;
 | 
					 | 
				
			||||||
    this.localCallFeed.dispose();
 | 
					 | 
				
			||||||
    this.localCallFeed = null;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    this.room = null;
 | 
					 | 
				
			||||||
    this.entered = false;
 | 
					 | 
				
			||||||
    this._left = true;
 | 
					 | 
				
			||||||
    this.participants = [];
 | 
					 | 
				
			||||||
    this.localParticipant = null;
 | 
					 | 
				
			||||||
    this.activeSpeaker = null;
 | 
					 | 
				
			||||||
    this.audioMuted = false;
 | 
					 | 
				
			||||||
    this.videoMuted = false;
 | 
					 | 
				
			||||||
    clearTimeout(this._memberParticipantStateTimeout);
 | 
					 | 
				
			||||||
    clearTimeout(this._activeSpeakerLoopTimeout);
 | 
					 | 
				
			||||||
    this._speakerMap.clear();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    this.emit("participants_changed");
 | 
					 | 
				
			||||||
    this.emit("left");
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  async getLocalVideoStream() {
 | 
					 | 
				
			||||||
    if (this.localVideoStream) {
 | 
					 | 
				
			||||||
      return this.localVideoStream;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    const stream = await this.client.getLocalVideoStream();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    this.localVideoStream = stream;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    return stream;
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  setAudioMuted(muted) {
 | 
					 | 
				
			||||||
    this.audioMuted = muted;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    if (this.localCallFeed) {
 | 
					 | 
				
			||||||
      this.localCallFeed.setAudioMuted(muted);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    const localStream = this.localVideoStream;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    if (localStream) {
 | 
					 | 
				
			||||||
      for (const track of localStream.getTracks()) {
 | 
					 | 
				
			||||||
        if (track.kind === "audio") {
 | 
					 | 
				
			||||||
          track.enabled = !this.audioMuted;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    for (let participant of this.participants) {
 | 
					 | 
				
			||||||
      const call = participant.call;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      if (
 | 
					 | 
				
			||||||
        call &&
 | 
					 | 
				
			||||||
        call.localUsermediaStream &&
 | 
					 | 
				
			||||||
        call.isMicrophoneMuted() !== this.audioMuted
 | 
					 | 
				
			||||||
      ) {
 | 
					 | 
				
			||||||
        call.setMicrophoneMuted(this.audioMuted);
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    this.emit("participants_changed");
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  setVideoMuted(muted) {
 | 
					 | 
				
			||||||
    this.videoMuted = muted;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    if (this.localCallFeed) {
 | 
					 | 
				
			||||||
      this.localCallFeed.setVideoMuted(muted);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    const localStream = this.localVideoStream;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    if (localStream) {
 | 
					 | 
				
			||||||
      for (const track of localStream.getTracks()) {
 | 
					 | 
				
			||||||
        if (track.kind === "video") {
 | 
					 | 
				
			||||||
          track.enabled = !this.videoMuted;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    for (let participant of this.participants) {
 | 
					 | 
				
			||||||
      const call = participant.call;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      if (
 | 
					 | 
				
			||||||
        call &&
 | 
					 | 
				
			||||||
        call.localUsermediaStream &&
 | 
					 | 
				
			||||||
        call.isLocalVideoMuted() !== this.videoMuted
 | 
					 | 
				
			||||||
      ) {
 | 
					 | 
				
			||||||
        call.setLocalVideoMuted(this.videoMuted);
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    this.emit("participants_changed");
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  logout() {
 | 
					 | 
				
			||||||
    localStorage.removeItem("matrix-auth-store");
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  /**
 | 
					 | 
				
			||||||
   * Call presence
 | 
					 | 
				
			||||||
   */
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  _updateMemberParticipantState = () => {
 | 
					 | 
				
			||||||
    const userId = this.client.getUserId();
 | 
					 | 
				
			||||||
    const currentMemberState = this.room.currentState.getStateEvents(
 | 
					 | 
				
			||||||
      "m.room.member",
 | 
					 | 
				
			||||||
      userId
 | 
					 | 
				
			||||||
    );
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    this._sendStateEventWithRetry(
 | 
					 | 
				
			||||||
      this.room.roomId,
 | 
					 | 
				
			||||||
      "m.room.member",
 | 
					 | 
				
			||||||
      {
 | 
					 | 
				
			||||||
        ...currentMemberState.getContent(),
 | 
					 | 
				
			||||||
        [CONF_PARTICIPANT]: {
 | 
					 | 
				
			||||||
          sessionId: this.sessionId,
 | 
					 | 
				
			||||||
          expiresAt: new Date().getTime() + PARTICIPANT_TIMEOUT * 2,
 | 
					 | 
				
			||||||
        },
 | 
					 | 
				
			||||||
      },
 | 
					 | 
				
			||||||
      userId
 | 
					 | 
				
			||||||
    );
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    const now = new Date().getTime();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    for (const participant of this.participants) {
 | 
					 | 
				
			||||||
      if (participant.local) {
 | 
					 | 
				
			||||||
        continue;
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      const memberStateEvent = this.room.currentState.getStateEvents(
 | 
					 | 
				
			||||||
        "m.room.member",
 | 
					 | 
				
			||||||
        participant.userId
 | 
					 | 
				
			||||||
      );
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      const memberStateContent = memberStateEvent.getContent();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      if (
 | 
					 | 
				
			||||||
        !memberStateContent ||
 | 
					 | 
				
			||||||
        !memberStateContent[CONF_PARTICIPANT] ||
 | 
					 | 
				
			||||||
        typeof memberStateContent[CONF_PARTICIPANT] !== "object" ||
 | 
					 | 
				
			||||||
        (memberStateContent[CONF_PARTICIPANT].expiresAt &&
 | 
					 | 
				
			||||||
          memberStateContent[CONF_PARTICIPANT].expiresAt < now)
 | 
					 | 
				
			||||||
      ) {
 | 
					 | 
				
			||||||
        this.emit("debugstate", participant.userId, null, "inactive");
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        if (participant.call) {
 | 
					 | 
				
			||||||
          // NOTE: This should remove the participant on the next tick
 | 
					 | 
				
			||||||
          // since matrix-js-sdk awaits a promise before firing user_hangup
 | 
					 | 
				
			||||||
          participant.call.hangup("user_hangup", false);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    this._memberParticipantStateTimeout = setTimeout(
 | 
					 | 
				
			||||||
      this._updateMemberParticipantState,
 | 
					 | 
				
			||||||
      PARTICIPANT_TIMEOUT
 | 
					 | 
				
			||||||
    );
 | 
					 | 
				
			||||||
  };
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  /**
 | 
					 | 
				
			||||||
   * Call Setup
 | 
					 | 
				
			||||||
   *
 | 
					 | 
				
			||||||
   * There are two different paths for calls to be created:
 | 
					 | 
				
			||||||
   * 1. Incoming calls triggered by the Call.incoming event.
 | 
					 | 
				
			||||||
   * 2. Outgoing calls to the initial members of a room or new members
 | 
					 | 
				
			||||||
   *    as they are observed by the RoomState.members event.
 | 
					 | 
				
			||||||
   */
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  _onIncomingCall = (call) => {
 | 
					 | 
				
			||||||
    // If we haven't entered yet, add the call to a queue which we'll use later.
 | 
					 | 
				
			||||||
    if (!this.entered) {
 | 
					 | 
				
			||||||
      this._incomingCallQueue.push(call);
 | 
					 | 
				
			||||||
      return;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    // The incoming calls may be for another room, which we will ignore.
 | 
					 | 
				
			||||||
    if (call.roomId !== this.room.roomId) {
 | 
					 | 
				
			||||||
      return;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    if (call.state !== "ringing") {
 | 
					 | 
				
			||||||
      console.warn("Incoming call no longer in ringing state. Ignoring.");
 | 
					 | 
				
			||||||
      return;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    // Get the remote video stream if it exists.
 | 
					 | 
				
			||||||
    const remoteFeed = call.getRemoteFeeds()[0];
 | 
					 | 
				
			||||||
    const stream = remoteFeed && remoteFeed.stream;
 | 
					 | 
				
			||||||
    const audioMuted = remoteFeed ? remoteFeed.isAudioMuted() : false;
 | 
					 | 
				
			||||||
    const videoMuted = remoteFeed ? remoteFeed.isVideoMuted() : false;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    const userId = call.opponentMember.userId;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    const memberStateEvent = this.room.currentState.getStateEvents(
 | 
					 | 
				
			||||||
      "m.room.member",
 | 
					 | 
				
			||||||
      userId
 | 
					 | 
				
			||||||
    );
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    const memberStateContent = memberStateEvent.getContent();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    if (!memberStateContent || !memberStateContent[CONF_PARTICIPANT]) {
 | 
					 | 
				
			||||||
      call.reject();
 | 
					 | 
				
			||||||
      return;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    const { sessionId } = memberStateContent[CONF_PARTICIPANT];
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    // Check if the user calling has an existing participant and use this call instead.
 | 
					 | 
				
			||||||
    const existingParticipant = this.participants.find(
 | 
					 | 
				
			||||||
      (p) => p.userId === userId
 | 
					 | 
				
			||||||
    );
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    let participant;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    console.log(call.opponentMember);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    if (existingParticipant) {
 | 
					 | 
				
			||||||
      participant = existingParticipant;
 | 
					 | 
				
			||||||
      // This also fires the hangup event and triggers those side-effects
 | 
					 | 
				
			||||||
      existingParticipant.call.hangup("replaced", false);
 | 
					 | 
				
			||||||
      existingParticipant.call = call;
 | 
					 | 
				
			||||||
      existingParticipant.stream = stream;
 | 
					 | 
				
			||||||
      existingParticipant.audioMuted = audioMuted;
 | 
					 | 
				
			||||||
      existingParticipant.videoMuted = videoMuted;
 | 
					 | 
				
			||||||
      existingParticipant.speaking = false;
 | 
					 | 
				
			||||||
      existingParticipant.activeSpeaker = false;
 | 
					 | 
				
			||||||
      existingParticipant.sessionId = sessionId;
 | 
					 | 
				
			||||||
    } else {
 | 
					 | 
				
			||||||
      participant = {
 | 
					 | 
				
			||||||
        local: false,
 | 
					 | 
				
			||||||
        userId,
 | 
					 | 
				
			||||||
        displayName: call.opponentMember.rawDisplayName,
 | 
					 | 
				
			||||||
        sessionId,
 | 
					 | 
				
			||||||
        call,
 | 
					 | 
				
			||||||
        stream,
 | 
					 | 
				
			||||||
        audioMuted,
 | 
					 | 
				
			||||||
        videoMuted,
 | 
					 | 
				
			||||||
        speaking: false,
 | 
					 | 
				
			||||||
        activeSpeaker: false,
 | 
					 | 
				
			||||||
      };
 | 
					 | 
				
			||||||
      this.participants.push(participant);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    if (remoteFeed) {
 | 
					 | 
				
			||||||
      remoteFeed.on("mute_state_changed", () =>
 | 
					 | 
				
			||||||
        this._onCallFeedMuteStateChanged(participant, remoteFeed)
 | 
					 | 
				
			||||||
      );
 | 
					 | 
				
			||||||
      remoteFeed.setSpeakingThreshold(SPEAKING_THRESHOLD);
 | 
					 | 
				
			||||||
      remoteFeed.measureVolumeActivity(true);
 | 
					 | 
				
			||||||
      remoteFeed.on("speaking", (speaking) => {
 | 
					 | 
				
			||||||
        this._onCallFeedSpeaking(participant, speaking);
 | 
					 | 
				
			||||||
      });
 | 
					 | 
				
			||||||
      remoteFeed.on("volume_changed", (maxVolume) =>
 | 
					 | 
				
			||||||
        this._onCallFeedVolumeChange(participant, maxVolume)
 | 
					 | 
				
			||||||
      );
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    call.on("state", (state) =>
 | 
					 | 
				
			||||||
      this._onCallStateChanged(participant, call, state)
 | 
					 | 
				
			||||||
    );
 | 
					 | 
				
			||||||
    call.on("feeds_changed", () => this._onCallFeedsChanged(participant, call));
 | 
					 | 
				
			||||||
    call.on("replaced", (newCall) =>
 | 
					 | 
				
			||||||
      this._onCallReplaced(participant, call, newCall)
 | 
					 | 
				
			||||||
    );
 | 
					 | 
				
			||||||
    call.on("hangup", () => this._onCallHangup(participant, call));
 | 
					 | 
				
			||||||
    call.answer();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    this.emit("call", call);
 | 
					 | 
				
			||||||
    this.emit("participants_changed");
 | 
					 | 
				
			||||||
  };
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  _onRoomStateMembers = (_event, _state, member) => {
 | 
					 | 
				
			||||||
    this._onMemberChanged(member);
 | 
					 | 
				
			||||||
  };
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  _onMemberChanged = (member) => {
 | 
					 | 
				
			||||||
    // Don't process new members until we've entered the conference call.
 | 
					 | 
				
			||||||
    if (!this.entered) {
 | 
					 | 
				
			||||||
      return;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    // The member events may be received for another room, which we will ignore.
 | 
					 | 
				
			||||||
    if (member.roomId !== this.room.roomId) {
 | 
					 | 
				
			||||||
      return;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    // Don't process your own member.
 | 
					 | 
				
			||||||
    const localUserId = this.client.getUserId();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    if (member.userId === localUserId) {
 | 
					 | 
				
			||||||
      return;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    // Get the latest member participant state event.
 | 
					 | 
				
			||||||
    const memberStateEvent = this.room.currentState.getStateEvents(
 | 
					 | 
				
			||||||
      "m.room.member",
 | 
					 | 
				
			||||||
      member.userId
 | 
					 | 
				
			||||||
    );
 | 
					 | 
				
			||||||
    const memberStateContent = memberStateEvent.getContent();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    if (!memberStateContent) {
 | 
					 | 
				
			||||||
      return;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    const participantInfo = memberStateContent[CONF_PARTICIPANT];
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    if (!participantInfo || typeof participantInfo !== "object") {
 | 
					 | 
				
			||||||
      return;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    const { expiresAt, sessionId } = participantInfo;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    // If the participant state has expired, ignore this user.
 | 
					 | 
				
			||||||
    const now = new Date().getTime();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    if (expiresAt < now) {
 | 
					 | 
				
			||||||
      this.emit("debugstate", member.userId, null, "inactive");
 | 
					 | 
				
			||||||
      return;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    // If there is an existing participant for this member check the session id.
 | 
					 | 
				
			||||||
    // If the session id changed then we can hang up the old call and start a new one.
 | 
					 | 
				
			||||||
    // Otherwise, ignore the member change event because we already have an active participant.
 | 
					 | 
				
			||||||
    let participant = this.participants.find((p) => p.userId === member.userId);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    if (participant) {
 | 
					 | 
				
			||||||
      if (participant.sessionId !== sessionId) {
 | 
					 | 
				
			||||||
        this.emit("debugstate", member.userId, null, "inactive");
 | 
					 | 
				
			||||||
        participant.call.hangup("replaced", false);
 | 
					 | 
				
			||||||
      } else {
 | 
					 | 
				
			||||||
        return;
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    // Only initiate a call with a user who has a userId that is lexicographically
 | 
					 | 
				
			||||||
    // less than your own. Otherwise, that user will call you.
 | 
					 | 
				
			||||||
    if (member.userId < localUserId) {
 | 
					 | 
				
			||||||
      this.emit("debugstate", member.userId, null, "waiting for invite");
 | 
					 | 
				
			||||||
      return;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    const call = this.client.createCall(this.room.roomId, member.userId);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    if (participant) {
 | 
					 | 
				
			||||||
      participant.sessionId = sessionId;
 | 
					 | 
				
			||||||
      participant.call = call;
 | 
					 | 
				
			||||||
      participant.stream = null;
 | 
					 | 
				
			||||||
      participant.audioMuted = false;
 | 
					 | 
				
			||||||
      participant.videoMuted = false;
 | 
					 | 
				
			||||||
      participant.speaking = false;
 | 
					 | 
				
			||||||
      participant.activeSpeaker = false;
 | 
					 | 
				
			||||||
    } else {
 | 
					 | 
				
			||||||
      participant = {
 | 
					 | 
				
			||||||
        local: false,
 | 
					 | 
				
			||||||
        userId: member.userId,
 | 
					 | 
				
			||||||
        displayName: member.rawDisplayName,
 | 
					 | 
				
			||||||
        sessionId,
 | 
					 | 
				
			||||||
        call,
 | 
					 | 
				
			||||||
        stream: null,
 | 
					 | 
				
			||||||
        audioMuted: false,
 | 
					 | 
				
			||||||
        videoMuted: false,
 | 
					 | 
				
			||||||
        speaking: false,
 | 
					 | 
				
			||||||
        activeSpeaker: false,
 | 
					 | 
				
			||||||
      };
 | 
					 | 
				
			||||||
      // TODO: Should we wait until the call has been answered to push the participant?
 | 
					 | 
				
			||||||
      // Or do we hide the participant until their stream is live?
 | 
					 | 
				
			||||||
      // Does hiding a participant without a stream present a privacy problem because
 | 
					 | 
				
			||||||
      // a participant without a stream can still listen in on other user's streams?
 | 
					 | 
				
			||||||
      this.participants.push(participant);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    call.on("state", (state) =>
 | 
					 | 
				
			||||||
      this._onCallStateChanged(participant, call, state)
 | 
					 | 
				
			||||||
    );
 | 
					 | 
				
			||||||
    call.on("feeds_changed", () => this._onCallFeedsChanged(participant, call));
 | 
					 | 
				
			||||||
    call.on("replaced", (newCall) =>
 | 
					 | 
				
			||||||
      this._onCallReplaced(participant, call, newCall)
 | 
					 | 
				
			||||||
    );
 | 
					 | 
				
			||||||
    call.on("hangup", () => this._onCallHangup(participant, call));
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    call.placeVideoCall().then(() => {
 | 
					 | 
				
			||||||
      this.emit("call", call);
 | 
					 | 
				
			||||||
    });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    this.emit("participants_changed");
 | 
					 | 
				
			||||||
  };
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  /**
 | 
					 | 
				
			||||||
   * Call Event Handlers
 | 
					 | 
				
			||||||
   */
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  _onCallStateChanged = (participant, call, state) => {
 | 
					 | 
				
			||||||
    if (
 | 
					 | 
				
			||||||
      call.localUsermediaStream &&
 | 
					 | 
				
			||||||
      call.isMicrophoneMuted() !== this.audioMuted
 | 
					 | 
				
			||||||
    ) {
 | 
					 | 
				
			||||||
      call.setMicrophoneMuted(this.audioMuted);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    if (
 | 
					 | 
				
			||||||
      call.localUsermediaStream &&
 | 
					 | 
				
			||||||
      call.isLocalVideoMuted() !== this.videoMuted
 | 
					 | 
				
			||||||
    ) {
 | 
					 | 
				
			||||||
      call.setLocalVideoMuted(this.videoMuted);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    this.emit("debugstate", participant.userId, call.callId, state);
 | 
					 | 
				
			||||||
  };
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  _onCallFeedsChanged = (participant, call) => {
 | 
					 | 
				
			||||||
    const remoteFeed = call.getRemoteFeeds()[0];
 | 
					 | 
				
			||||||
    const stream = remoteFeed && remoteFeed.stream;
 | 
					 | 
				
			||||||
    const audioMuted = remoteFeed ? remoteFeed.isAudioMuted() : false;
 | 
					 | 
				
			||||||
    const videoMuted = remoteFeed ? remoteFeed.isVideoMuted() : false;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    if (remoteFeed && participant.stream !== stream) {
 | 
					 | 
				
			||||||
      participant.stream = stream;
 | 
					 | 
				
			||||||
      participant.audioMuted = audioMuted;
 | 
					 | 
				
			||||||
      participant.videoMuted = videoMuted;
 | 
					 | 
				
			||||||
      remoteFeed.on("mute_state_changed", () =>
 | 
					 | 
				
			||||||
        this._onCallFeedMuteStateChanged(participant, remoteFeed)
 | 
					 | 
				
			||||||
      );
 | 
					 | 
				
			||||||
      remoteFeed.setSpeakingThreshold(SPEAKING_THRESHOLD);
 | 
					 | 
				
			||||||
      remoteFeed.measureVolumeActivity(true);
 | 
					 | 
				
			||||||
      remoteFeed.on("speaking", (speaking) => {
 | 
					 | 
				
			||||||
        this._onCallFeedSpeaking(participant, speaking);
 | 
					 | 
				
			||||||
      });
 | 
					 | 
				
			||||||
      remoteFeed.on("volume_changed", (maxVolume) =>
 | 
					 | 
				
			||||||
        this._onCallFeedVolumeChange(participant, maxVolume)
 | 
					 | 
				
			||||||
      );
 | 
					 | 
				
			||||||
      this._onCallFeedMuteStateChanged(participant, remoteFeed);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  };
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  _onCallFeedMuteStateChanged = (participant, feed) => {
 | 
					 | 
				
			||||||
    participant.audioMuted = feed.isAudioMuted();
 | 
					 | 
				
			||||||
    participant.videoMuted = feed.isVideoMuted();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    if (participant.audioMuted) {
 | 
					 | 
				
			||||||
      this._speakerMap.set(
 | 
					 | 
				
			||||||
        participant.userId,
 | 
					 | 
				
			||||||
        Array(ACTIVE_SPEAKER_SAMPLES).fill(-Infinity)
 | 
					 | 
				
			||||||
      );
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    this.emit("participants_changed");
 | 
					 | 
				
			||||||
  };
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  _onCallFeedSpeaking = (participant, speaking) => {
 | 
					 | 
				
			||||||
    participant.speaking = speaking;
 | 
					 | 
				
			||||||
    this.emit("participants_changed");
 | 
					 | 
				
			||||||
  };
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  _onCallFeedVolumeChange = (participant, maxVolume) => {
 | 
					 | 
				
			||||||
    if (!this._speakerMap.has(participant.userId)) {
 | 
					 | 
				
			||||||
      this._speakerMap.set(
 | 
					 | 
				
			||||||
        participant.userId,
 | 
					 | 
				
			||||||
        Array(ACTIVE_SPEAKER_SAMPLES).fill(-Infinity)
 | 
					 | 
				
			||||||
      );
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    const volumeArr = this._speakerMap.get(participant.userId);
 | 
					 | 
				
			||||||
    volumeArr.shift();
 | 
					 | 
				
			||||||
    volumeArr.push(maxVolume);
 | 
					 | 
				
			||||||
  };
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  _onActiveSpeakerLoop = () => {
 | 
					 | 
				
			||||||
    let topAvg;
 | 
					 | 
				
			||||||
    let activeSpeakerId;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    for (const [userId, volumeArr] of this._speakerMap) {
 | 
					 | 
				
			||||||
      let total = 0;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      for (let i = 0; i < volumeArr.length; i++) {
 | 
					 | 
				
			||||||
        const volume = volumeArr[i];
 | 
					 | 
				
			||||||
        total += Math.max(volume, SPEAKING_THRESHOLD);
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      const avg = total / ACTIVE_SPEAKER_SAMPLES;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      if (!topAvg) {
 | 
					 | 
				
			||||||
        topAvg = avg;
 | 
					 | 
				
			||||||
        activeSpeakerId = userId;
 | 
					 | 
				
			||||||
      } else if (avg > topAvg) {
 | 
					 | 
				
			||||||
        topAvg = avg;
 | 
					 | 
				
			||||||
        activeSpeakerId = userId;
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    if (activeSpeakerId && topAvg > SPEAKING_THRESHOLD) {
 | 
					 | 
				
			||||||
      const nextActiveSpeaker = this.participants.find(
 | 
					 | 
				
			||||||
        (p) => p.userId === activeSpeakerId
 | 
					 | 
				
			||||||
      );
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      if (nextActiveSpeaker && this.activeSpeaker !== nextActiveSpeaker) {
 | 
					 | 
				
			||||||
        this.activeSpeaker.activeSpeaker = false;
 | 
					 | 
				
			||||||
        nextActiveSpeaker.activeSpeaker = true;
 | 
					 | 
				
			||||||
        this.activeSpeaker = nextActiveSpeaker;
 | 
					 | 
				
			||||||
        this.emit("participants_changed");
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    this._activeSpeakerLoopTimeout = setTimeout(
 | 
					 | 
				
			||||||
      this._onActiveSpeakerLoop,
 | 
					 | 
				
			||||||
      ACTIVE_SPEAKER_INTERVAL
 | 
					 | 
				
			||||||
    );
 | 
					 | 
				
			||||||
  };
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  _onCallReplaced = (participant, call, newCall) => {
 | 
					 | 
				
			||||||
    participant.call = newCall;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    newCall.on("state", (state) =>
 | 
					 | 
				
			||||||
      this._onCallStateChanged(participant, newCall, state)
 | 
					 | 
				
			||||||
    );
 | 
					 | 
				
			||||||
    newCall.on("feeds_changed", () =>
 | 
					 | 
				
			||||||
      this._onCallFeedsChanged(participant, newCall)
 | 
					 | 
				
			||||||
    );
 | 
					 | 
				
			||||||
    newCall.on("replaced", (nextCall) =>
 | 
					 | 
				
			||||||
      this._onCallReplaced(participant, newCall, nextCall)
 | 
					 | 
				
			||||||
    );
 | 
					 | 
				
			||||||
    newCall.on("hangup", () => this._onCallHangup(participant, newCall));
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    const remoteFeed = newCall.getRemoteFeeds()[0];
 | 
					 | 
				
			||||||
    participant.stream = remoteFeed ? remoteFeed.stream : null;
 | 
					 | 
				
			||||||
    participant.audioMuted = remoteFeed ? remoteFeed.isAudioMuted() : false;
 | 
					 | 
				
			||||||
    participant.videoMuted = remoteFeed ? remoteFeed.isVideoMuted() : false;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    if (remoteFeed) {
 | 
					 | 
				
			||||||
      remoteFeed.on("mute_state_changed", () =>
 | 
					 | 
				
			||||||
        this._onCallFeedMuteStateChanged(participant, remoteFeed)
 | 
					 | 
				
			||||||
      );
 | 
					 | 
				
			||||||
      remoteFeed.setSpeakingThreshold(SPEAKING_THRESHOLD);
 | 
					 | 
				
			||||||
      remoteFeed.measureVolumeActivity(true);
 | 
					 | 
				
			||||||
      remoteFeed.on("speaking", (speaking) => {
 | 
					 | 
				
			||||||
        this._onCallFeedSpeaking(participant, speaking);
 | 
					 | 
				
			||||||
      });
 | 
					 | 
				
			||||||
      remoteFeed.on("volume_changed", (maxVolume) =>
 | 
					 | 
				
			||||||
        this._onCallFeedVolumeChange(participant, maxVolume)
 | 
					 | 
				
			||||||
      );
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    this.emit("call", newCall);
 | 
					 | 
				
			||||||
    this.emit("participants_changed");
 | 
					 | 
				
			||||||
  };
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  _onCallHangup = (participant, call) => {
 | 
					 | 
				
			||||||
    if (call.hangupReason === "replaced") {
 | 
					 | 
				
			||||||
      return;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    const participantIndex = this.participants.indexOf(participant);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    if (participantIndex === -1) {
 | 
					 | 
				
			||||||
      return;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    this.participants.splice(participantIndex, 1);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    if (this.activeSpeaker === participant && this.participants.length > 0) {
 | 
					 | 
				
			||||||
      this.activeSpeaker = this.participants[0];
 | 
					 | 
				
			||||||
      this.activeSpeaker.activeSpeaker = true;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    this._speakerMap.delete(participant.userId);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    this.emit("participants_changed");
 | 
					 | 
				
			||||||
  };
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  /**
 | 
					 | 
				
			||||||
   * Utils
 | 
					 | 
				
			||||||
   */
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  _sendStateEventWithRetry(
 | 
					 | 
				
			||||||
    roomId,
 | 
					 | 
				
			||||||
    eventType,
 | 
					 | 
				
			||||||
    content,
 | 
					 | 
				
			||||||
    stateKey,
 | 
					 | 
				
			||||||
    callback,
 | 
					 | 
				
			||||||
    maxAttempts = 5
 | 
					 | 
				
			||||||
  ) {
 | 
					 | 
				
			||||||
    const sendStateEventWithRetry = async (attempt = 0) => {
 | 
					 | 
				
			||||||
      try {
 | 
					 | 
				
			||||||
        return await this.client.sendStateEvent(
 | 
					 | 
				
			||||||
          roomId,
 | 
					 | 
				
			||||||
          eventType,
 | 
					 | 
				
			||||||
          content,
 | 
					 | 
				
			||||||
          stateKey,
 | 
					 | 
				
			||||||
          callback
 | 
					 | 
				
			||||||
        );
 | 
					 | 
				
			||||||
      } catch (error) {
 | 
					 | 
				
			||||||
        if (attempt >= maxAttempts) {
 | 
					 | 
				
			||||||
          throw error;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        await new Promise((resolve) => setTimeout(resolve(), 5));
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        return sendStateEventWithRetry(attempt + 1);
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    };
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    return sendStateEventWithRetry();
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
| 
						 | 
					@ -14,8 +14,8 @@ See the License for the specific language governing permissions and
 | 
				
			||||||
limitations under the License.
 | 
					limitations under the License.
 | 
				
			||||||
*/
 | 
					*/
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import { useCallback, useEffect, useState } from "react";
 | 
					import { useCallback, useEffect, useRef, useState } from "react";
 | 
				
			||||||
import { ConferenceCallManager } from "./ConferenceCallManager";
 | 
					import matrix from "matrix-js-sdk";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// https://stackoverflow.com/a/9039885
 | 
					// https://stackoverflow.com/a/9039885
 | 
				
			||||||
function isIOS() {
 | 
					function isIOS() {
 | 
				
			||||||
| 
						 | 
					@ -33,289 +33,230 @@ function isIOS() {
 | 
				
			||||||
  );
 | 
					  );
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export function useConferenceCallManager(homeserverUrl) {
 | 
					function waitForSync(client) {
 | 
				
			||||||
  const [{ loading, authenticated, manager, error }, setState] = useState({
 | 
					  return new Promise((resolve, reject) => {
 | 
				
			||||||
 | 
					    const onSync = (state) => {
 | 
				
			||||||
 | 
					      if (state === "PREPARED") {
 | 
				
			||||||
 | 
					        resolve();
 | 
				
			||||||
 | 
					        client.removeListener("sync", onSync);
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					    client.on("sync", onSync);
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					async function initClient(clientOptions, guest) {
 | 
				
			||||||
 | 
					  const client = matrix.createClient(clientOptions);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  if (guest) {
 | 
				
			||||||
 | 
					    client.setGuest(true);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  await client.startClient({
 | 
				
			||||||
 | 
					    // dirty hack to reduce chance of gappy syncs
 | 
				
			||||||
 | 
					    // should be fixed by spotting gaps and backpaginating
 | 
				
			||||||
 | 
					    initialSyncLimit: 50,
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  await waitForSync(client);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  return client;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export async function fetchRoom(client, roomId, join, timeout = 5000) {
 | 
				
			||||||
 | 
					  let room = client.getRoom(roomId);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  if (room) {
 | 
				
			||||||
 | 
					    return room;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  if (join) {
 | 
				
			||||||
 | 
					    room = await client.joinRoom(roomId);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (room) {
 | 
				
			||||||
 | 
					      return room;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  return new Promise((resolve, reject) => {
 | 
				
			||||||
 | 
					    let timeoutId;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    function onRoom(room) {
 | 
				
			||||||
 | 
					      if (room && room.roomId === roomId) {
 | 
				
			||||||
 | 
					        clearTimeout(timeoutId);
 | 
				
			||||||
 | 
					        client.removeListener("Room", onRoom);
 | 
				
			||||||
 | 
					        resolve(room);
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const room = client.getRoom(roomId);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (room) {
 | 
				
			||||||
 | 
					      resolve(room);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    client.on("Room", onRoom);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (timeout) {
 | 
				
			||||||
 | 
					      timeoutId = setTimeout(() => {
 | 
				
			||||||
 | 
					        client.removeListener("Room", onRoom);
 | 
				
			||||||
 | 
					        reject(new Error("Fetching room timed out."));
 | 
				
			||||||
 | 
					      }, timeout);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export function useClient(homeserverUrl) {
 | 
				
			||||||
 | 
					  const [{ loading, authenticated, client }, setState] = useState({
 | 
				
			||||||
    loading: true,
 | 
					    loading: true,
 | 
				
			||||||
    authenticated: false,
 | 
					    authenticated: false,
 | 
				
			||||||
    manager: undefined,
 | 
					    client: undefined,
 | 
				
			||||||
    error: undefined,
 | 
					 | 
				
			||||||
  });
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  useEffect(() => {
 | 
					  useEffect(() => {
 | 
				
			||||||
    ConferenceCallManager.restore(homeserverUrl)
 | 
					    async function restore() {
 | 
				
			||||||
      .then((manager) => {
 | 
					      try {
 | 
				
			||||||
        setState({
 | 
					        const authStore = localStorage.getItem("matrix-auth-store");
 | 
				
			||||||
          manager,
 | 
					 | 
				
			||||||
          loading: false,
 | 
					 | 
				
			||||||
          authenticated: !!manager,
 | 
					 | 
				
			||||||
          error: undefined,
 | 
					 | 
				
			||||||
        });
 | 
					 | 
				
			||||||
      })
 | 
					 | 
				
			||||||
      .catch((err) => {
 | 
					 | 
				
			||||||
        console.error(err);
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
        setState({
 | 
					        if (authStore) {
 | 
				
			||||||
          manager: undefined,
 | 
					          const { user_id, device_id, access_token } = JSON.parse(authStore);
 | 
				
			||||||
          loading: false,
 | 
					 | 
				
			||||||
          authenticated: false,
 | 
					 | 
				
			||||||
          error: err,
 | 
					 | 
				
			||||||
        });
 | 
					 | 
				
			||||||
      });
 | 
					 | 
				
			||||||
  }, []);
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const login = useCallback(async (username, password, cb) => {
 | 
					          const client = await initClient({
 | 
				
			||||||
    setState((prevState) => ({
 | 
					            baseUrl: homeserverUrl,
 | 
				
			||||||
      ...prevState,
 | 
					            accessToken: access_token,
 | 
				
			||||||
      authenticated: false,
 | 
					            userId: user_id,
 | 
				
			||||||
      error: undefined,
 | 
					            deviceId: device_id,
 | 
				
			||||||
    }));
 | 
					          });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    ConferenceCallManager.login(homeserverUrl, username, password)
 | 
					          localStorage.setItem(
 | 
				
			||||||
      .then((manager) => {
 | 
					            "matrix-auth-store",
 | 
				
			||||||
        setState({
 | 
					            JSON.stringify({ user_id, device_id, access_token })
 | 
				
			||||||
          manager,
 | 
					          );
 | 
				
			||||||
          loading: false,
 | 
					 | 
				
			||||||
          authenticated: true,
 | 
					 | 
				
			||||||
          error: undefined,
 | 
					 | 
				
			||||||
        });
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if (cb) {
 | 
					          return client;
 | 
				
			||||||
          cb();
 | 
					        }
 | 
				
			||||||
 | 
					      } catch (err) {
 | 
				
			||||||
 | 
					        localStorage.removeItem("matrix-auth-store");
 | 
				
			||||||
 | 
					        throw err;
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    restore()
 | 
				
			||||||
 | 
					      .then((client) => {
 | 
				
			||||||
 | 
					        if (client) {
 | 
				
			||||||
 | 
					          setState({ client, loading: false, authenticated: true });
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					          setState({ client: undefined, loading: false, authenticated: false });
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
      })
 | 
					      })
 | 
				
			||||||
      .catch((err) => {
 | 
					      .catch(() => {
 | 
				
			||||||
        console.error(err);
 | 
					        setState({ client: undefined, loading: false, authenticated: false });
 | 
				
			||||||
 | 
					 | 
				
			||||||
        setState({
 | 
					 | 
				
			||||||
          manager: undefined,
 | 
					 | 
				
			||||||
          loading: false,
 | 
					 | 
				
			||||||
          authenticated: false,
 | 
					 | 
				
			||||||
          error: err,
 | 
					 | 
				
			||||||
        });
 | 
					 | 
				
			||||||
      });
 | 
					      });
 | 
				
			||||||
  }, []);
 | 
					  }, []);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const loginAsGuest = useCallback(async (displayName) => {
 | 
					  const login = useCallback(async (username, password) => {
 | 
				
			||||||
    setState((prevState) => ({
 | 
					    try {
 | 
				
			||||||
      ...prevState,
 | 
					      const registrationClient = matrix.createClient(homeserverUrl);
 | 
				
			||||||
      authenticated: false,
 | 
					 | 
				
			||||||
      error: undefined,
 | 
					 | 
				
			||||||
    }));
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    ConferenceCallManager.loginAsGuest(homeserverUrl, displayName)
 | 
					      const { user_id, device_id, access_token } =
 | 
				
			||||||
      .then((manager) => {
 | 
					        await registrationClient.loginWithPassword(username, password);
 | 
				
			||||||
        setState({
 | 
					 | 
				
			||||||
          manager,
 | 
					 | 
				
			||||||
          loading: false,
 | 
					 | 
				
			||||||
          authenticated: true,
 | 
					 | 
				
			||||||
          error: undefined,
 | 
					 | 
				
			||||||
        });
 | 
					 | 
				
			||||||
      })
 | 
					 | 
				
			||||||
      .catch((err) => {
 | 
					 | 
				
			||||||
        console.error(err);
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
        setState({
 | 
					      const client = await initClient({
 | 
				
			||||||
          manager: undefined,
 | 
					        baseUrl: homeserverUrl,
 | 
				
			||||||
          loading: false,
 | 
					        accessToken: access_token,
 | 
				
			||||||
          authenticated: false,
 | 
					        userId: user_id,
 | 
				
			||||||
          error: err,
 | 
					        deviceId: device_id,
 | 
				
			||||||
        });
 | 
					 | 
				
			||||||
      });
 | 
					      });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      localStorage.setItem(
 | 
				
			||||||
 | 
					        "matrix-auth-store",
 | 
				
			||||||
 | 
					        JSON.stringify({ user_id, device_id, access_token })
 | 
				
			||||||
 | 
					      );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      setState({ client, loading: false, authenticated: true });
 | 
				
			||||||
 | 
					    } catch (err) {
 | 
				
			||||||
 | 
					      localStorage.removeItem("matrix-auth-store");
 | 
				
			||||||
 | 
					      setState({ client: undefined, loading: false, authenticated: false });
 | 
				
			||||||
 | 
					      throw err;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
  }, []);
 | 
					  }, []);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const register = useCallback(async (username, password, cb) => {
 | 
					  const registerGuest = useCallback(async (displayName) => {
 | 
				
			||||||
    setState((prevState) => ({
 | 
					    try {
 | 
				
			||||||
      ...prevState,
 | 
					      const registrationClient = matrix.createClient(homeserverUrl);
 | 
				
			||||||
      authenticated: false,
 | 
					 | 
				
			||||||
      error: undefined,
 | 
					 | 
				
			||||||
    }));
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    ConferenceCallManager.register(homeserverUrl, username, password)
 | 
					      const { user_id, device_id, access_token } =
 | 
				
			||||||
      .then((manager) => {
 | 
					        await registrationClient.registerGuest({});
 | 
				
			||||||
        setState({
 | 
					 | 
				
			||||||
          manager,
 | 
					 | 
				
			||||||
          loading: false,
 | 
					 | 
				
			||||||
          authenticated: true,
 | 
					 | 
				
			||||||
          error: undefined,
 | 
					 | 
				
			||||||
        });
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if (cb) {
 | 
					      const client = await initClient(
 | 
				
			||||||
          cb();
 | 
					        {
 | 
				
			||||||
        }
 | 
					          baseUrl: homeserverUrl,
 | 
				
			||||||
      })
 | 
					          accessToken: access_token,
 | 
				
			||||||
      .catch((err) => {
 | 
					          userId: user_id,
 | 
				
			||||||
        console.error(err);
 | 
					          deviceId: device_id,
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        true
 | 
				
			||||||
 | 
					      );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        setState({
 | 
					      localStorage.setItem(
 | 
				
			||||||
          manager: undefined,
 | 
					        "matrix-auth-store",
 | 
				
			||||||
          loading: false,
 | 
					        JSON.stringify({ user_id, device_id, access_token })
 | 
				
			||||||
          authenticated: false,
 | 
					      );
 | 
				
			||||||
          error: err,
 | 
					
 | 
				
			||||||
        });
 | 
					      setState({ client, loading: false, authenticated: true });
 | 
				
			||||||
      });
 | 
					    } catch (err) {
 | 
				
			||||||
 | 
					      localStorage.removeItem("matrix-auth-store");
 | 
				
			||||||
 | 
					      setState({ client: undefined, loading: false, authenticated: false });
 | 
				
			||||||
 | 
					      throw err;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
  }, []);
 | 
					  }, []);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  useEffect(() => {
 | 
					  const register = useCallback(async (username, password) => {
 | 
				
			||||||
    window.confManager = manager;
 | 
					    try {
 | 
				
			||||||
 | 
					      const registrationClient = matrix.createClient(homeserverUrl);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    return () => {
 | 
					      const { user_id, device_id, access_token } =
 | 
				
			||||||
      window.confManager = undefined;
 | 
					        await registrationClient.register(username, password, null, {
 | 
				
			||||||
    };
 | 
					          type: "m.login.dummy",
 | 
				
			||||||
  }, [manager]);
 | 
					        });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      const client = await initClient({
 | 
				
			||||||
 | 
					        baseUrl: homeserverUrl,
 | 
				
			||||||
 | 
					        accessToken: access_token,
 | 
				
			||||||
 | 
					        userId: user_id,
 | 
				
			||||||
 | 
					        deviceId: device_id,
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      localStorage.setItem(
 | 
				
			||||||
 | 
					        "matrix-auth-store",
 | 
				
			||||||
 | 
					        JSON.stringify({ user_id, device_id, access_token })
 | 
				
			||||||
 | 
					      );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      setState({ client, loading: false, authenticated: true });
 | 
				
			||||||
 | 
					    } catch (err) {
 | 
				
			||||||
 | 
					      localStorage.removeItem("matrix-auth-store");
 | 
				
			||||||
 | 
					      setState({ client: undefined, loading: false, authenticated: false });
 | 
				
			||||||
 | 
					      throw err;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }, []);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const logout = useCallback(() => {
 | 
				
			||||||
 | 
					    localStorage.removeItem("matrix-auth-store");
 | 
				
			||||||
 | 
					    setState({ client: undefined, loading: false, authenticated: false });
 | 
				
			||||||
 | 
					  }, []);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  return {
 | 
					  return {
 | 
				
			||||||
    loading,
 | 
					    loading,
 | 
				
			||||||
    authenticated,
 | 
					    authenticated,
 | 
				
			||||||
    manager,
 | 
					    client,
 | 
				
			||||||
    error,
 | 
					 | 
				
			||||||
    login,
 | 
					    login,
 | 
				
			||||||
    loginAsGuest,
 | 
					    registerGuest,
 | 
				
			||||||
    register,
 | 
					    register,
 | 
				
			||||||
 | 
					    logout,
 | 
				
			||||||
  };
 | 
					  };
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export function useVideoRoom(manager, roomId, timeout = 5000) {
 | 
					function usePageUnload(callback) {
 | 
				
			||||||
  const [
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
      loading,
 | 
					 | 
				
			||||||
      joined,
 | 
					 | 
				
			||||||
      joining,
 | 
					 | 
				
			||||||
      room,
 | 
					 | 
				
			||||||
      participants,
 | 
					 | 
				
			||||||
      error,
 | 
					 | 
				
			||||||
      videoMuted,
 | 
					 | 
				
			||||||
      audioMuted,
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
    setState,
 | 
					 | 
				
			||||||
  ] = useState({
 | 
					 | 
				
			||||||
    loading: true,
 | 
					 | 
				
			||||||
    joining: false,
 | 
					 | 
				
			||||||
    joined: false,
 | 
					 | 
				
			||||||
    room: undefined,
 | 
					 | 
				
			||||||
    participants: [],
 | 
					 | 
				
			||||||
    error: undefined,
 | 
					 | 
				
			||||||
    videoMuted: false,
 | 
					 | 
				
			||||||
    audioMuted: false,
 | 
					 | 
				
			||||||
  });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  useEffect(() => {
 | 
					 | 
				
			||||||
    setState((prevState) => ({
 | 
					 | 
				
			||||||
      ...prevState,
 | 
					 | 
				
			||||||
      loading: true,
 | 
					 | 
				
			||||||
      room: undefined,
 | 
					 | 
				
			||||||
      error: undefined,
 | 
					 | 
				
			||||||
    }));
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    const onParticipantsChanged = () => {
 | 
					 | 
				
			||||||
      setState((prevState) => ({
 | 
					 | 
				
			||||||
        ...prevState,
 | 
					 | 
				
			||||||
        participants: [...manager.participants],
 | 
					 | 
				
			||||||
      }));
 | 
					 | 
				
			||||||
    };
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    manager.on("participants_changed", onParticipantsChanged);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    manager.client.joinRoom(roomId).catch((err) => {
 | 
					 | 
				
			||||||
      setState((prevState) => ({ ...prevState, loading: false, error: err }));
 | 
					 | 
				
			||||||
    });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    let timeoutId;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    function roomCallback(room) {
 | 
					 | 
				
			||||||
      if (room && room.roomId === roomId) {
 | 
					 | 
				
			||||||
        clearTimeout(timeoutId);
 | 
					 | 
				
			||||||
        manager.client.removeListener("Room", roomCallback);
 | 
					 | 
				
			||||||
        setState((prevState) => ({
 | 
					 | 
				
			||||||
          ...prevState,
 | 
					 | 
				
			||||||
          loading: false,
 | 
					 | 
				
			||||||
          room,
 | 
					 | 
				
			||||||
          error: undefined,
 | 
					 | 
				
			||||||
        }));
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    let initialRoom = manager.client.getRoom(roomId);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    if (initialRoom) {
 | 
					 | 
				
			||||||
      setState((prevState) => ({
 | 
					 | 
				
			||||||
        ...prevState,
 | 
					 | 
				
			||||||
        loading: false,
 | 
					 | 
				
			||||||
        room: initialRoom,
 | 
					 | 
				
			||||||
        error: undefined,
 | 
					 | 
				
			||||||
      }));
 | 
					 | 
				
			||||||
    } else {
 | 
					 | 
				
			||||||
      manager.client.on("Room", roomCallback);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      timeoutId = setTimeout(() => {
 | 
					 | 
				
			||||||
        setState((prevState) => ({
 | 
					 | 
				
			||||||
          ...prevState,
 | 
					 | 
				
			||||||
          loading: false,
 | 
					 | 
				
			||||||
          room: undefined,
 | 
					 | 
				
			||||||
          error: new Error("Room could not be found."),
 | 
					 | 
				
			||||||
        }));
 | 
					 | 
				
			||||||
        manager.client.removeListener("Room", roomCallback);
 | 
					 | 
				
			||||||
      }, timeout);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    function onLeaveCall() {
 | 
					 | 
				
			||||||
      setState((prevState) => ({
 | 
					 | 
				
			||||||
        ...prevState,
 | 
					 | 
				
			||||||
        videoMuted: manager.videoMuted,
 | 
					 | 
				
			||||||
        audioMuted: manager.audioMuted,
 | 
					 | 
				
			||||||
      }));
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    manager.on("left", onLeaveCall);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    return () => {
 | 
					 | 
				
			||||||
      manager.client.removeListener("Room", roomCallback);
 | 
					 | 
				
			||||||
      manager.removeListener("participants_changed", onParticipantsChanged);
 | 
					 | 
				
			||||||
      clearTimeout(timeoutId);
 | 
					 | 
				
			||||||
      manager.leaveCall();
 | 
					 | 
				
			||||||
      manager.removeListener("left", onLeaveCall);
 | 
					 | 
				
			||||||
    };
 | 
					 | 
				
			||||||
  }, [manager, roomId]);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  const joinCall = useCallback(() => {
 | 
					 | 
				
			||||||
    if (joining || joined) {
 | 
					 | 
				
			||||||
      return;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    setState((prevState) => ({
 | 
					 | 
				
			||||||
      ...prevState,
 | 
					 | 
				
			||||||
      joining: true,
 | 
					 | 
				
			||||||
    }));
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    manager
 | 
					 | 
				
			||||||
      .enter(roomId)
 | 
					 | 
				
			||||||
      .then(() => {
 | 
					 | 
				
			||||||
        setState((prevState) => ({
 | 
					 | 
				
			||||||
          ...prevState,
 | 
					 | 
				
			||||||
          joining: false,
 | 
					 | 
				
			||||||
          joined: true,
 | 
					 | 
				
			||||||
        }));
 | 
					 | 
				
			||||||
      })
 | 
					 | 
				
			||||||
      .catch((error) => {
 | 
					 | 
				
			||||||
        setState((prevState) => ({
 | 
					 | 
				
			||||||
          ...prevState,
 | 
					 | 
				
			||||||
          joining: false,
 | 
					 | 
				
			||||||
          joined: false,
 | 
					 | 
				
			||||||
          error,
 | 
					 | 
				
			||||||
        }));
 | 
					 | 
				
			||||||
      });
 | 
					 | 
				
			||||||
  }, [manager, roomId, joining, joined]);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  const leaveCall = useCallback(() => {
 | 
					 | 
				
			||||||
    manager.leaveCall();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    setState((prevState) => ({
 | 
					 | 
				
			||||||
      ...prevState,
 | 
					 | 
				
			||||||
      participants: [...manager.participants],
 | 
					 | 
				
			||||||
      joined: false,
 | 
					 | 
				
			||||||
      joining: false,
 | 
					 | 
				
			||||||
    }));
 | 
					 | 
				
			||||||
  }, [manager]);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  useEffect(() => {
 | 
					  useEffect(() => {
 | 
				
			||||||
    let pageVisibilityTimeout;
 | 
					    let pageVisibilityTimeout;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -327,25 +268,11 @@ export function useVideoRoom(manager, roomId, timeout = 5000) {
 | 
				
			||||||
          // Wait 5 seconds before closing the page to avoid accidentally leaving
 | 
					          // Wait 5 seconds before closing the page to avoid accidentally leaving
 | 
				
			||||||
          // TODO: Make this configurable?
 | 
					          // TODO: Make this configurable?
 | 
				
			||||||
          pageVisibilityTimeout = setTimeout(() => {
 | 
					          pageVisibilityTimeout = setTimeout(() => {
 | 
				
			||||||
            manager.leaveCall();
 | 
					            callback();
 | 
				
			||||||
 | 
					 | 
				
			||||||
            setState((prevState) => ({
 | 
					 | 
				
			||||||
              ...prevState,
 | 
					 | 
				
			||||||
              participants: [...manager.participants],
 | 
					 | 
				
			||||||
              joined: false,
 | 
					 | 
				
			||||||
              joining: false,
 | 
					 | 
				
			||||||
            }));
 | 
					 | 
				
			||||||
          }, 5000);
 | 
					          }, 5000);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
      } else {
 | 
					      } else {
 | 
				
			||||||
        manager.leaveCall();
 | 
					        callback();
 | 
				
			||||||
 | 
					 | 
				
			||||||
        setState((prevState) => ({
 | 
					 | 
				
			||||||
          ...prevState,
 | 
					 | 
				
			||||||
          participants: [...manager.participants],
 | 
					 | 
				
			||||||
          joined: false,
 | 
					 | 
				
			||||||
          joining: false,
 | 
					 | 
				
			||||||
        }));
 | 
					 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -363,31 +290,174 @@ export function useVideoRoom(manager, roomId, timeout = 5000) {
 | 
				
			||||||
      window.removeEventListener("beforeunload", onBeforeUnload);
 | 
					      window.removeEventListener("beforeunload", onBeforeUnload);
 | 
				
			||||||
      clearTimeout(pageVisibilityTimeout);
 | 
					      clearTimeout(pageVisibilityTimeout);
 | 
				
			||||||
    };
 | 
					    };
 | 
				
			||||||
  }, [manager]);
 | 
					  }, []);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const toggleMuteAudio = useCallback(() => {
 | 
					function getParticipants(groupCall) {
 | 
				
			||||||
    manager.setAudioMuted(!manager.audioMuted);
 | 
					  return [...groupCall.participants];
 | 
				
			||||||
    setState((prevState) => ({ ...prevState, audioMuted: manager.audioMuted }));
 | 
					}
 | 
				
			||||||
  }, [manager]);
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const toggleMuteVideo = useCallback(() => {
 | 
					export function useGroupCall(client, roomId) {
 | 
				
			||||||
    manager.setVideoMuted(!manager.videoMuted);
 | 
					  const groupCallRef = useRef(null);
 | 
				
			||||||
    setState((prevState) => ({ ...prevState, videoMuted: manager.videoMuted }));
 | 
					
 | 
				
			||||||
  }, [manager]);
 | 
					  const [
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					      loading,
 | 
				
			||||||
 | 
					      entered,
 | 
				
			||||||
 | 
					      entering,
 | 
				
			||||||
 | 
					      room,
 | 
				
			||||||
 | 
					      participants,
 | 
				
			||||||
 | 
					      error,
 | 
				
			||||||
 | 
					      microphoneMuted,
 | 
				
			||||||
 | 
					      localVideoMuted,
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    setState,
 | 
				
			||||||
 | 
					  ] = useState({
 | 
				
			||||||
 | 
					    loading: true,
 | 
				
			||||||
 | 
					    entered: false,
 | 
				
			||||||
 | 
					    entering: false,
 | 
				
			||||||
 | 
					    room: null,
 | 
				
			||||||
 | 
					    participants: [],
 | 
				
			||||||
 | 
					    error: null,
 | 
				
			||||||
 | 
					    microphoneMuted: false,
 | 
				
			||||||
 | 
					    localVideoMuted: false,
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const updateState = (state) =>
 | 
				
			||||||
 | 
					    setState((prevState) => ({ ...prevState, ...state }));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  useEffect(() => {
 | 
				
			||||||
 | 
					    function onParticipantsChanged(...args) {
 | 
				
			||||||
 | 
					      console.log(...args);
 | 
				
			||||||
 | 
					      updateState({ participants: getParticipants(groupCallRef.current) });
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    function onLocalMuteStateChanged(microphoneMuted, localVideoMuted) {
 | 
				
			||||||
 | 
					      updateState({
 | 
				
			||||||
 | 
					        microphoneMuted,
 | 
				
			||||||
 | 
					        localVideoMuted,
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    async function init() {
 | 
				
			||||||
 | 
					      const room = await fetchRoom(client, roomId, true);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      const groupCall = client.createGroupCall(roomId, "video");
 | 
				
			||||||
 | 
					      groupCallRef.current = groupCall;
 | 
				
			||||||
 | 
					      groupCall.on("active_speaker_changed", onParticipantsChanged);
 | 
				
			||||||
 | 
					      groupCall.on("participants_changed", onParticipantsChanged);
 | 
				
			||||||
 | 
					      groupCall.on("speaking", onParticipantsChanged);
 | 
				
			||||||
 | 
					      groupCall.on("mute_state_changed", onParticipantsChanged);
 | 
				
			||||||
 | 
					      groupCall.on("participant_call_replaced", onParticipantsChanged);
 | 
				
			||||||
 | 
					      groupCall.on("participant_call_feeds_changed", onParticipantsChanged);
 | 
				
			||||||
 | 
					      groupCall.on("local_mute_state_changed", onLocalMuteStateChanged);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      updateState({
 | 
				
			||||||
 | 
					        room,
 | 
				
			||||||
 | 
					        loading: false,
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    init().catch((error) => {
 | 
				
			||||||
 | 
					      if (groupCallRef.current) {
 | 
				
			||||||
 | 
					        const groupCall = groupCallRef.current;
 | 
				
			||||||
 | 
					        groupCall.removeListener(
 | 
				
			||||||
 | 
					          "active_speaker_changed",
 | 
				
			||||||
 | 
					          onParticipantsChanged
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
 | 
					        groupCall.removeListener("participants_changed", onParticipantsChanged);
 | 
				
			||||||
 | 
					        groupCall.removeListener("speaking", onParticipantsChanged);
 | 
				
			||||||
 | 
					        groupCall.removeListener("mute_state_changed", onParticipantsChanged);
 | 
				
			||||||
 | 
					        groupCall.removeListener(
 | 
				
			||||||
 | 
					          "participant_call_replaced",
 | 
				
			||||||
 | 
					          onParticipantsChanged
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
 | 
					        groupCall.removeListener(
 | 
				
			||||||
 | 
					          "participant_call_feeds_changed",
 | 
				
			||||||
 | 
					          onParticipantsChanged
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
 | 
					        groupCall.removeListener(
 | 
				
			||||||
 | 
					          "local_mute_state_changed",
 | 
				
			||||||
 | 
					          onLocalMuteStateChanged
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
 | 
					        groupCall.leave();
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      updateState({ error, loading: false });
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return () => {
 | 
				
			||||||
 | 
					      if (groupCallRef.current) {
 | 
				
			||||||
 | 
					        groupCallRef.current.leave();
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					  }, [client, roomId]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  usePageUnload(() => {
 | 
				
			||||||
 | 
					    if (groupCallRef.current) {
 | 
				
			||||||
 | 
					      groupCallRef.current.leave();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const initLocalParticipant = useCallback(
 | 
				
			||||||
 | 
					    () => groupCallRef.current.initLocalParticipant(),
 | 
				
			||||||
 | 
					    []
 | 
				
			||||||
 | 
					  );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const enter = useCallback(() => {
 | 
				
			||||||
 | 
					    updateState({ entering: true });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    groupCallRef.current
 | 
				
			||||||
 | 
					      .enter()
 | 
				
			||||||
 | 
					      .then(() => {
 | 
				
			||||||
 | 
					        updateState({
 | 
				
			||||||
 | 
					          entered: true,
 | 
				
			||||||
 | 
					          entering: false,
 | 
				
			||||||
 | 
					          participants: getParticipants(groupCallRef.current),
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					      })
 | 
				
			||||||
 | 
					      .catch((error) => {
 | 
				
			||||||
 | 
					        updateState({ error, entering: false });
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
 | 
					  }, []);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const leave = useCallback(() => {
 | 
				
			||||||
 | 
					    groupCallRef.current.leave();
 | 
				
			||||||
 | 
					    updateState({
 | 
				
			||||||
 | 
					      entered: false,
 | 
				
			||||||
 | 
					      participants: [],
 | 
				
			||||||
 | 
					      microphoneMuted: false,
 | 
				
			||||||
 | 
					      localVideoMuted: false,
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					  }, []);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const toggleLocalVideoMuted = useCallback(() => {
 | 
				
			||||||
 | 
					    groupCallRef.current.setLocalVideoMuted(
 | 
				
			||||||
 | 
					      !groupCallRef.current.isLocalVideoMuted()
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					  }, []);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const toggleMicrophoneMuted = useCallback(() => {
 | 
				
			||||||
 | 
					    groupCallRef.current.setMicrophoneMuted(
 | 
				
			||||||
 | 
					      !groupCallRef.current.isMicrophoneMuted()
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					  }, []);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  return {
 | 
					  return {
 | 
				
			||||||
    loading,
 | 
					    loading,
 | 
				
			||||||
    joined,
 | 
					    entered,
 | 
				
			||||||
    joining,
 | 
					    entering,
 | 
				
			||||||
    room,
 | 
					    roomName: room ? room.name : null,
 | 
				
			||||||
    participants,
 | 
					    participants,
 | 
				
			||||||
 | 
					    groupCall: groupCallRef.current,
 | 
				
			||||||
 | 
					    microphoneMuted,
 | 
				
			||||||
 | 
					    localVideoMuted,
 | 
				
			||||||
    error,
 | 
					    error,
 | 
				
			||||||
    joinCall,
 | 
					    initLocalParticipant,
 | 
				
			||||||
    leaveCall,
 | 
					    enter,
 | 
				
			||||||
    toggleMuteVideo,
 | 
					    leave,
 | 
				
			||||||
    toggleMuteAudio,
 | 
					    toggleLocalVideoMuted,
 | 
				
			||||||
    videoMuted,
 | 
					    toggleMicrophoneMuted,
 | 
				
			||||||
    audioMuted,
 | 
					 | 
				
			||||||
  };
 | 
					  };
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -440,22 +510,22 @@ function sortRooms(client, rooms) {
 | 
				
			||||||
  });
 | 
					  });
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export function useRooms(manager) {
 | 
					export function useRooms(client) {
 | 
				
			||||||
  const [rooms, setRooms] = useState([]);
 | 
					  const [rooms, setRooms] = useState([]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  useEffect(() => {
 | 
					  useEffect(() => {
 | 
				
			||||||
    function updateRooms() {
 | 
					    function updateRooms() {
 | 
				
			||||||
      const visibleRooms = manager.client.getVisibleRooms();
 | 
					      const visibleRooms = client.getVisibleRooms();
 | 
				
			||||||
      const sortedRooms = sortRooms(manager.client, visibleRooms);
 | 
					      const sortedRooms = sortRooms(client, visibleRooms);
 | 
				
			||||||
      setRooms(sortedRooms);
 | 
					      setRooms(sortedRooms);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    updateRooms();
 | 
					    updateRooms();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    manager.client.on("Room", updateRooms);
 | 
					    client.on("Room", updateRooms);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    return () => {
 | 
					    return () => {
 | 
				
			||||||
      manager.client.removeListener("Room", updateRooms);
 | 
					      client.removeListener("Room", updateRooms);
 | 
				
			||||||
    };
 | 
					    };
 | 
				
			||||||
  }, []);
 | 
					  }, []);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										91
									
								
								src/GuestAuthPage.jsx
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										91
									
								
								src/GuestAuthPage.jsx
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,91 @@
 | 
				
			||||||
 | 
					/*
 | 
				
			||||||
 | 
					Copyright 2021 New Vector Ltd
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Licensed under the Apache License, Version 2.0 (the "License");
 | 
				
			||||||
 | 
					you may not use this file except in compliance with the License.
 | 
				
			||||||
 | 
					You may obtain a copy of the License at
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    http://www.apache.org/licenses/LICENSE-2.0
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Unless required by applicable law or agreed to in writing, software
 | 
				
			||||||
 | 
					distributed under the License is distributed on an "AS IS" BASIS,
 | 
				
			||||||
 | 
					WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | 
				
			||||||
 | 
					See the License for the specific language governing permissions and
 | 
				
			||||||
 | 
					limitations under the License.
 | 
				
			||||||
 | 
					*/
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import React, { useState, useRef, useCallback } from "react";
 | 
				
			||||||
 | 
					import styles from "./GuestAuthPage.module.css";
 | 
				
			||||||
 | 
					import { useLocation, useHistory, Link } from "react-router-dom";
 | 
				
			||||||
 | 
					import { Header, LeftNav } from "./Header";
 | 
				
			||||||
 | 
					import { Button, FieldRow, InputField, ErrorMessage } from "./Input";
 | 
				
			||||||
 | 
					import { Center, Content, Info, Modal } from "./Layout";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export function GuestAuthPage({ onLoginAsGuest }) {
 | 
				
			||||||
 | 
					  const displayNameRef = useRef();
 | 
				
			||||||
 | 
					  const history = useHistory();
 | 
				
			||||||
 | 
					  const location = useLocation();
 | 
				
			||||||
 | 
					  const [error, setError] = useState();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const onSubmitLoginForm = useCallback(
 | 
				
			||||||
 | 
					    (e) => {
 | 
				
			||||||
 | 
					      e.preventDefault();
 | 
				
			||||||
 | 
					      onLoginAsGuest(displayNameRef.current.value).catch(setError);
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    [onLoginAsGuest, location, history]
 | 
				
			||||||
 | 
					  );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  return (
 | 
				
			||||||
 | 
					    <div className={styles.guestAuthPage}>
 | 
				
			||||||
 | 
					      <Header>
 | 
				
			||||||
 | 
					        <LeftNav />
 | 
				
			||||||
 | 
					      </Header>
 | 
				
			||||||
 | 
					      <Content>
 | 
				
			||||||
 | 
					        <Center>
 | 
				
			||||||
 | 
					          <Modal>
 | 
				
			||||||
 | 
					            <h2>Login As Guest</h2>
 | 
				
			||||||
 | 
					            <form onSubmit={onSubmitLoginForm}>
 | 
				
			||||||
 | 
					              <FieldRow>
 | 
				
			||||||
 | 
					                <InputField
 | 
				
			||||||
 | 
					                  type="text"
 | 
				
			||||||
 | 
					                  ref={displayNameRef}
 | 
				
			||||||
 | 
					                  placeholder="Display Name"
 | 
				
			||||||
 | 
					                  label="Display Name"
 | 
				
			||||||
 | 
					                  autoCorrect="off"
 | 
				
			||||||
 | 
					                  autoCapitalize="none"
 | 
				
			||||||
 | 
					                />
 | 
				
			||||||
 | 
					              </FieldRow>
 | 
				
			||||||
 | 
					              {error && (
 | 
				
			||||||
 | 
					                <FieldRow>
 | 
				
			||||||
 | 
					                  <ErrorMessage>{error.message}</ErrorMessage>
 | 
				
			||||||
 | 
					                </FieldRow>
 | 
				
			||||||
 | 
					              )}
 | 
				
			||||||
 | 
					              <FieldRow rightAlign>
 | 
				
			||||||
 | 
					                <Button type="submit">Login as guest</Button>
 | 
				
			||||||
 | 
					              </FieldRow>
 | 
				
			||||||
 | 
					            </form>
 | 
				
			||||||
 | 
					            <Info>
 | 
				
			||||||
 | 
					              <Link
 | 
				
			||||||
 | 
					                to={{
 | 
				
			||||||
 | 
					                  pathname: "/login",
 | 
				
			||||||
 | 
					                  state: location.state,
 | 
				
			||||||
 | 
					                }}
 | 
				
			||||||
 | 
					              >
 | 
				
			||||||
 | 
					                Sign in
 | 
				
			||||||
 | 
					              </Link>
 | 
				
			||||||
 | 
					              {" or "}
 | 
				
			||||||
 | 
					              <Link
 | 
				
			||||||
 | 
					                to={{
 | 
				
			||||||
 | 
					                  pathname: "/register",
 | 
				
			||||||
 | 
					                  state: location.state,
 | 
				
			||||||
 | 
					                }}
 | 
				
			||||||
 | 
					              >
 | 
				
			||||||
 | 
					                Create account
 | 
				
			||||||
 | 
					              </Link>
 | 
				
			||||||
 | 
					            </Info>
 | 
				
			||||||
 | 
					          </Modal>
 | 
				
			||||||
 | 
					        </Center>
 | 
				
			||||||
 | 
					      </Content>
 | 
				
			||||||
 | 
					    </div>
 | 
				
			||||||
 | 
					  );
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										8
									
								
								src/GuestAuthPage.module.css
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								src/GuestAuthPage.module.css
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,8 @@
 | 
				
			||||||
 | 
					.guestAuthPage {
 | 
				
			||||||
 | 
					  position: relative;
 | 
				
			||||||
 | 
					  display: flex;
 | 
				
			||||||
 | 
					  flex-direction: column;
 | 
				
			||||||
 | 
					  width: 100vw;
 | 
				
			||||||
 | 
					  height: 100vh;
 | 
				
			||||||
 | 
					  overflow: hidden;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										27
									
								
								src/Home.jsx
									
										
									
									
									
								
							
							
						
						
									
										27
									
								
								src/Home.jsx
									
										
									
									
									
								
							| 
						 | 
					@ -25,12 +25,12 @@ import { Center, Content, Modal } from "./Layout";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const colorHash = new ColorHash({ lightness: 0.3 });
 | 
					const colorHash = new ColorHash({ lightness: 0.3 });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export function Home({ manager }) {
 | 
					export function Home({ client, onLogout }) {
 | 
				
			||||||
  const history = useHistory();
 | 
					  const history = useHistory();
 | 
				
			||||||
  const roomNameRef = useRef();
 | 
					  const roomNameRef = useRef();
 | 
				
			||||||
  const guestAccessRef = useRef();
 | 
					  const guestAccessRef = useRef();
 | 
				
			||||||
  const [createRoomError, setCreateRoomError] = useState();
 | 
					  const [createRoomError, setCreateRoomError] = useState();
 | 
				
			||||||
  const rooms = useRooms(manager);
 | 
					  const rooms = useRooms(client);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const onCreateRoom = useCallback(
 | 
					  const onCreateRoom = useCallback(
 | 
				
			||||||
    (e) => {
 | 
					    (e) => {
 | 
				
			||||||
| 
						 | 
					@ -38,7 +38,7 @@ export function Home({ manager }) {
 | 
				
			||||||
      setCreateRoomError(undefined);
 | 
					      setCreateRoomError(undefined);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      async function createRoom(name, guestAccess) {
 | 
					      async function createRoom(name, guestAccess) {
 | 
				
			||||||
        const { room_id } = await manager.client.createRoom({
 | 
					        const { room_id } = await client.createRoom({
 | 
				
			||||||
          visibility: "private",
 | 
					          visibility: "private",
 | 
				
			||||||
          preset: "public_chat",
 | 
					          preset: "public_chat",
 | 
				
			||||||
          name,
 | 
					          name,
 | 
				
			||||||
| 
						 | 
					@ -62,14 +62,14 @@ export function Home({ manager }) {
 | 
				
			||||||
                  "m.sticker": 50,
 | 
					                  "m.sticker": 50,
 | 
				
			||||||
                },
 | 
					                },
 | 
				
			||||||
                users: {
 | 
					                users: {
 | 
				
			||||||
                  [manager.client.getUserId()]: 100,
 | 
					                  [client.getUserId()]: 100,
 | 
				
			||||||
                },
 | 
					                },
 | 
				
			||||||
              }
 | 
					              }
 | 
				
			||||||
            : undefined,
 | 
					            : undefined,
 | 
				
			||||||
        });
 | 
					        });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if (guestAccess) {
 | 
					        if (guestAccess) {
 | 
				
			||||||
          await manager.client.setGuestAccess(room_id, {
 | 
					          await client.setGuestAccess(room_id, {
 | 
				
			||||||
            allowJoin: true,
 | 
					            allowJoin: true,
 | 
				
			||||||
            allowRead: true,
 | 
					            allowRead: true,
 | 
				
			||||||
          });
 | 
					          });
 | 
				
			||||||
| 
						 | 
					@ -83,27 +83,14 @@ export function Home({ manager }) {
 | 
				
			||||||
        guestAccessRef.current.checked
 | 
					        guestAccessRef.current.checked
 | 
				
			||||||
      ).catch(setCreateRoomError);
 | 
					      ).catch(setCreateRoomError);
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    [manager]
 | 
					    [client]
 | 
				
			||||||
  );
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  const onLogout = useCallback(
 | 
					 | 
				
			||||||
    (e) => {
 | 
					 | 
				
			||||||
      e.preventDefault();
 | 
					 | 
				
			||||||
      manager.logout();
 | 
					 | 
				
			||||||
      location.reload();
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
    [manager]
 | 
					 | 
				
			||||||
  );
 | 
					  );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  return (
 | 
					  return (
 | 
				
			||||||
    <>
 | 
					    <>
 | 
				
			||||||
      <Header>
 | 
					      <Header>
 | 
				
			||||||
        <LeftNav />
 | 
					        <LeftNav />
 | 
				
			||||||
        <UserNav
 | 
					        <UserNav signedIn userName={client.getUserId()} onLogout={onLogout} />
 | 
				
			||||||
          signedIn={manager.client}
 | 
					 | 
				
			||||||
          userName={manager.client.getUserId()}
 | 
					 | 
				
			||||||
          onLogout={onLogout}
 | 
					 | 
				
			||||||
        />
 | 
					 | 
				
			||||||
      </Header>
 | 
					      </Header>
 | 
				
			||||||
      <Content>
 | 
					      <Content>
 | 
				
			||||||
        <Center>
 | 
					        <Center>
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -20,26 +20,25 @@ import { Header, LeftNav } from "./Header";
 | 
				
			||||||
import { FieldRow, InputField, Button, ErrorMessage } from "./Input";
 | 
					import { FieldRow, InputField, Button, ErrorMessage } from "./Input";
 | 
				
			||||||
import { Center, Content, Info, Modal } from "./Layout";
 | 
					import { Center, Content, Info, Modal } from "./Layout";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export function LoginPage({ onLogin, error }) {
 | 
					export function LoginPage({ onLogin }) {
 | 
				
			||||||
  const loginUsernameRef = useRef();
 | 
					  const loginUsernameRef = useRef();
 | 
				
			||||||
  const loginPasswordRef = useRef();
 | 
					  const loginPasswordRef = useRef();
 | 
				
			||||||
  const history = useHistory();
 | 
					  const history = useHistory();
 | 
				
			||||||
  const location = useLocation();
 | 
					  const location = useLocation();
 | 
				
			||||||
 | 
					  const [error, setError] = useState();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const onSubmitLoginForm = useCallback(
 | 
					  const onSubmitLoginForm = useCallback(
 | 
				
			||||||
    (e) => {
 | 
					    (e) => {
 | 
				
			||||||
      e.preventDefault();
 | 
					      e.preventDefault();
 | 
				
			||||||
      onLogin(
 | 
					      onLogin(loginUsernameRef.current.value, loginPasswordRef.current.value)
 | 
				
			||||||
        loginUsernameRef.current.value,
 | 
					        .then(() => {
 | 
				
			||||||
        loginPasswordRef.current.value,
 | 
					 | 
				
			||||||
        () => {
 | 
					 | 
				
			||||||
          if (location.state && location.state.from) {
 | 
					          if (location.state && location.state.from) {
 | 
				
			||||||
            history.replace(location.state.from);
 | 
					            history.replace(location.state.from);
 | 
				
			||||||
          } else {
 | 
					          } else {
 | 
				
			||||||
            history.replace("/");
 | 
					            history.replace("/");
 | 
				
			||||||
          }
 | 
					          }
 | 
				
			||||||
        }
 | 
					        })
 | 
				
			||||||
      );
 | 
					        .catch(setError);
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    [onLogin, location, history]
 | 
					    [onLogin, location, history]
 | 
				
			||||||
  );
 | 
					  );
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -14,32 +14,34 @@ See the License for the specific language governing permissions and
 | 
				
			||||||
limitations under the License.
 | 
					limitations under the License.
 | 
				
			||||||
*/
 | 
					*/
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import React, { useCallback, useRef } from "react";
 | 
					import React, { useCallback, useRef, useState } from "react";
 | 
				
			||||||
import { useHistory, useLocation, Link } from "react-router-dom";
 | 
					import { useHistory, useLocation, Link } from "react-router-dom";
 | 
				
			||||||
import { Header, LeftNav } from "./Header";
 | 
					import { Header, LeftNav } from "./Header";
 | 
				
			||||||
import { FieldRow, InputField, Button, ErrorMessage } from "./Input";
 | 
					import { FieldRow, InputField, Button, ErrorMessage } from "./Input";
 | 
				
			||||||
import { Center, Content, Info, Modal } from "./Layout";
 | 
					import { Center, Content, Info, Modal } from "./Layout";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export function RegisterPage({ onRegister, error }) {
 | 
					export function RegisterPage({ onRegister }) {
 | 
				
			||||||
  const registerUsernameRef = useRef();
 | 
					  const registerUsernameRef = useRef();
 | 
				
			||||||
  const registerPasswordRef = useRef();
 | 
					  const registerPasswordRef = useRef();
 | 
				
			||||||
  const history = useHistory();
 | 
					  const history = useHistory();
 | 
				
			||||||
  const location = useLocation();
 | 
					  const location = useLocation();
 | 
				
			||||||
 | 
					  const [error, setError] = useState();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const onSubmitRegisterForm = useCallback(
 | 
					  const onSubmitRegisterForm = useCallback(
 | 
				
			||||||
    (e) => {
 | 
					    (e) => {
 | 
				
			||||||
      e.preventDefault();
 | 
					      e.preventDefault();
 | 
				
			||||||
      onRegister(
 | 
					      onRegister(
 | 
				
			||||||
        registerUsernameRef.current.value,
 | 
					        registerUsernameRef.current.value,
 | 
				
			||||||
        registerPasswordRef.current.value,
 | 
					        registerPasswordRef.current.value
 | 
				
			||||||
        () => {
 | 
					      )
 | 
				
			||||||
 | 
					        .then(() => {
 | 
				
			||||||
          if (location.state && location.state.from) {
 | 
					          if (location.state && location.state.from) {
 | 
				
			||||||
            history.replace(location.state.from);
 | 
					            history.replace(location.state.from);
 | 
				
			||||||
          } else {
 | 
					          } else {
 | 
				
			||||||
            history.replace("/");
 | 
					            history.replace("/");
 | 
				
			||||||
          }
 | 
					          }
 | 
				
			||||||
        }
 | 
					        })
 | 
				
			||||||
      );
 | 
					        .catch(setError);
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    [onRegister, location, history]
 | 
					    [onRegister, location, history]
 | 
				
			||||||
  );
 | 
					  );
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										394
									
								
								src/Room.jsx
									
										
									
									
									
								
							
							
						
						
									
										394
									
								
								src/Room.jsx
									
										
									
									
									
								
							| 
						 | 
					@ -23,7 +23,7 @@ import React, {
 | 
				
			||||||
} from "react";
 | 
					} from "react";
 | 
				
			||||||
import styles from "./Room.module.css";
 | 
					import styles from "./Room.module.css";
 | 
				
			||||||
import { useParams, useLocation, useHistory, Link } from "react-router-dom";
 | 
					import { useParams, useLocation, useHistory, Link } from "react-router-dom";
 | 
				
			||||||
import { useVideoRoom } from "./ConferenceCallManagerHooks";
 | 
					import { useGroupCall } from "./ConferenceCallManagerHooks";
 | 
				
			||||||
import { DevTools } from "./DevTools";
 | 
					import { DevTools } from "./DevTools";
 | 
				
			||||||
import { VideoGrid } from "./VideoGrid";
 | 
					import { VideoGrid } from "./VideoGrid";
 | 
				
			||||||
import {
 | 
					import {
 | 
				
			||||||
| 
						 | 
					@ -42,25 +42,16 @@ function useQuery() {
 | 
				
			||||||
  return useMemo(() => new URLSearchParams(location.search), [location.search]);
 | 
					  return useMemo(() => new URLSearchParams(location.search), [location.search]);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export function Room({ manager }) {
 | 
					function useDebugMode() {
 | 
				
			||||||
  const { roomId } = useParams();
 | 
					 | 
				
			||||||
  const query = useQuery();
 | 
					  const query = useQuery();
 | 
				
			||||||
  const {
 | 
					 | 
				
			||||||
    loading,
 | 
					 | 
				
			||||||
    joined,
 | 
					 | 
				
			||||||
    joining,
 | 
					 | 
				
			||||||
    room,
 | 
					 | 
				
			||||||
    participants,
 | 
					 | 
				
			||||||
    error,
 | 
					 | 
				
			||||||
    joinCall,
 | 
					 | 
				
			||||||
    leaveCall,
 | 
					 | 
				
			||||||
    toggleMuteVideo,
 | 
					 | 
				
			||||||
    toggleMuteAudio,
 | 
					 | 
				
			||||||
    videoMuted,
 | 
					 | 
				
			||||||
    audioMuted,
 | 
					 | 
				
			||||||
  } = useVideoRoom(manager, roomId);
 | 
					 | 
				
			||||||
  const debugStr = query.get("debug");
 | 
					  const debugStr = query.get("debug");
 | 
				
			||||||
  const [debug, setDebug] = useState(debugStr === "" || debugStr === "true");
 | 
					  const [debugMode, setDebugMode] = useState(
 | 
				
			||||||
 | 
					    debugStr === "" || debugStr === "true"
 | 
				
			||||||
 | 
					  );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const toggleDebugMode = useCallback(() => {
 | 
				
			||||||
 | 
					    setDebugMode((prevDebugMode) => !prevDebugMode);
 | 
				
			||||||
 | 
					  }, []);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  useEffect(() => {
 | 
					  useEffect(() => {
 | 
				
			||||||
    function onKeyDown(event) {
 | 
					    function onKeyDown(event) {
 | 
				
			||||||
| 
						 | 
					@ -68,7 +59,7 @@ export function Room({ manager }) {
 | 
				
			||||||
        document.activeElement.tagName !== "input" &&
 | 
					        document.activeElement.tagName !== "input" &&
 | 
				
			||||||
        event.code === "Backquote"
 | 
					        event.code === "Backquote"
 | 
				
			||||||
      ) {
 | 
					      ) {
 | 
				
			||||||
        setDebug((prevDebug) => !prevDebug);
 | 
					        toggleDebugMode();
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -79,190 +70,237 @@ export function Room({ manager }) {
 | 
				
			||||||
    };
 | 
					    };
 | 
				
			||||||
  }, []);
 | 
					  }, []);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  return [debugMode, toggleDebugMode];
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function useRoomLayout() {
 | 
				
			||||||
  const [layout, setLayout] = useState("gallery");
 | 
					  const [layout, setLayout] = useState("gallery");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const toggleLayout = useCallback(() => {
 | 
					  const toggleLayout = useCallback(() => {
 | 
				
			||||||
    setLayout(layout === "spotlight" ? "gallery" : "spotlight");
 | 
					    setLayout(layout === "spotlight" ? "gallery" : "spotlight");
 | 
				
			||||||
  }, [layout]);
 | 
					  }, [layout]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  return (
 | 
					  return [layout, toggleLayout];
 | 
				
			||||||
    <div className={styles.room}>
 | 
					}
 | 
				
			||||||
      {!loading && room && (
 | 
					
 | 
				
			||||||
        <Header>
 | 
					export function Room({ client }) {
 | 
				
			||||||
          <LeftNav />
 | 
					  const { roomId } = useParams();
 | 
				
			||||||
          <CenterNav>
 | 
					  const {
 | 
				
			||||||
            <h3>{room.name}</h3>
 | 
					    loading,
 | 
				
			||||||
          </CenterNav>
 | 
					    entered,
 | 
				
			||||||
          <RightNav>
 | 
					    entering,
 | 
				
			||||||
            {!loading && room && joined && (
 | 
					    roomName,
 | 
				
			||||||
              <LayoutToggleButton
 | 
					    participants,
 | 
				
			||||||
                title={layout === "spotlight" ? "Spotlight" : "Gallery"}
 | 
					    groupCall,
 | 
				
			||||||
                layout={layout}
 | 
					    microphoneMuted,
 | 
				
			||||||
                onClick={toggleLayout}
 | 
					    localVideoMuted,
 | 
				
			||||||
              />
 | 
					    error,
 | 
				
			||||||
            )}
 | 
					    initLocalParticipant,
 | 
				
			||||||
            <SettingsButton
 | 
					    enter,
 | 
				
			||||||
              title={debug ? "Disable DevTools" : "Enable DevTools"}
 | 
					    leave,
 | 
				
			||||||
              on={debug}
 | 
					    toggleLocalVideoMuted,
 | 
				
			||||||
              onClick={() => setDebug((debug) => !debug)}
 | 
					    toggleMicrophoneMuted,
 | 
				
			||||||
            />
 | 
					  } = useGroupCall(client, roomId);
 | 
				
			||||||
          </RightNav>
 | 
					
 | 
				
			||||||
        </Header>
 | 
					  const content = () => {
 | 
				
			||||||
      )}
 | 
					    if (error) {
 | 
				
			||||||
      {loading && (
 | 
					      return <LoadingErrorView error={error} />;
 | 
				
			||||||
        <div className={styles.centerMessage}>
 | 
					    }
 | 
				
			||||||
          <p>Loading room...</p>
 | 
					
 | 
				
			||||||
        </div>
 | 
					    if (loading) {
 | 
				
			||||||
      )}
 | 
					      return <LoadingRoomView />;
 | 
				
			||||||
      {error && <div className={styles.centerMessage}>{error.message}</div>}
 | 
					    }
 | 
				
			||||||
      {!loading && room && !joined && (
 | 
					
 | 
				
			||||||
        <JoinRoom
 | 
					    if (entering) {
 | 
				
			||||||
          manager={manager}
 | 
					      return <EnteringRoomView />;
 | 
				
			||||||
          joining={joining}
 | 
					    }
 | 
				
			||||||
          joinCall={joinCall}
 | 
					
 | 
				
			||||||
          toggleMuteVideo={toggleMuteVideo}
 | 
					    if (!entered) {
 | 
				
			||||||
          toggleMuteAudio={toggleMuteAudio}
 | 
					      return (
 | 
				
			||||||
          videoMuted={videoMuted}
 | 
					        <RoomSetupView
 | 
				
			||||||
          audioMuted={audioMuted}
 | 
					          roomName={roomName}
 | 
				
			||||||
 | 
					          onInitLocalParticipant={initLocalParticipant}
 | 
				
			||||||
 | 
					          onEnter={enter}
 | 
				
			||||||
 | 
					          microphoneMuted={microphoneMuted}
 | 
				
			||||||
 | 
					          localVideoMuted={localVideoMuted}
 | 
				
			||||||
 | 
					          toggleLocalVideoMuted={toggleLocalVideoMuted}
 | 
				
			||||||
 | 
					          toggleMicrophoneMuted={toggleMicrophoneMuted}
 | 
				
			||||||
        />
 | 
					        />
 | 
				
			||||||
      )}
 | 
					      );
 | 
				
			||||||
      {!loading && room && joined && participants.length === 0 && (
 | 
					    } else {
 | 
				
			||||||
        <div className={styles.centerMessage}>
 | 
					      return (
 | 
				
			||||||
          <p>Waiting for other participants...</p>
 | 
					        <InRoomView
 | 
				
			||||||
        </div>
 | 
					          roomName={roomName}
 | 
				
			||||||
      )}
 | 
					          microphoneMuted={microphoneMuted}
 | 
				
			||||||
      {!loading && room && joined && participants.length > 0 && (
 | 
					          localVideoMuted={localVideoMuted}
 | 
				
			||||||
        <VideoGrid participants={participants} layout={layout} />
 | 
					          toggleLocalVideoMuted={toggleLocalVideoMuted}
 | 
				
			||||||
      )}
 | 
					          toggleMicrophoneMuted={toggleMicrophoneMuted}
 | 
				
			||||||
      {!loading && room && joined && (
 | 
					          participants={participants}
 | 
				
			||||||
        <div className={styles.footer}>
 | 
					          onLeave={leave}
 | 
				
			||||||
          <MicButton muted={audioMuted} onClick={toggleMuteAudio} />
 | 
					          groupCall={groupCall}
 | 
				
			||||||
          <VideoButton enabled={videoMuted} onClick={toggleMuteVideo} />
 | 
					        />
 | 
				
			||||||
          <HangupButton onClick={leaveCall} />
 | 
					      );
 | 
				
			||||||
        </div>
 | 
					    }
 | 
				
			||||||
      )}
 | 
					  };
 | 
				
			||||||
      {debug && <DevTools manager={manager} />}
 | 
					
 | 
				
			||||||
    </div>
 | 
					  return <div className={styles.room}>{content()}</div>;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export function LoadingRoomView() {
 | 
				
			||||||
 | 
					  return (
 | 
				
			||||||
 | 
					    <>
 | 
				
			||||||
 | 
					      <div className={styles.centerMessage}>
 | 
				
			||||||
 | 
					        <p>Loading room...</p>
 | 
				
			||||||
 | 
					      </div>
 | 
				
			||||||
 | 
					    </>
 | 
				
			||||||
  );
 | 
					  );
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export function RoomAuth({ onLoginAsGuest, error }) {
 | 
					export function EnteringRoomView() {
 | 
				
			||||||
  const displayNameRef = useRef();
 | 
					  return (
 | 
				
			||||||
  const history = useHistory();
 | 
					    <>
 | 
				
			||||||
  const location = useLocation();
 | 
					      <div className={styles.centerMessage}>
 | 
				
			||||||
 | 
					        <p>Entering room...</p>
 | 
				
			||||||
  const onSubmitLoginForm = useCallback(
 | 
					      </div>
 | 
				
			||||||
    (e) => {
 | 
					    </>
 | 
				
			||||||
      e.preventDefault();
 | 
					 | 
				
			||||||
      onLoginAsGuest(displayNameRef.current.value);
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
    [onLoginAsGuest, location, history]
 | 
					 | 
				
			||||||
  );
 | 
					  );
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export function LoadingErrorView({ error }) {
 | 
				
			||||||
 | 
					  return (
 | 
				
			||||||
 | 
					    <>
 | 
				
			||||||
 | 
					      <div className={styles.centerMessage}>
 | 
				
			||||||
 | 
					        <ErrorMessage>{error.message}</ErrorMessage>
 | 
				
			||||||
 | 
					      </div>
 | 
				
			||||||
 | 
					    </>
 | 
				
			||||||
 | 
					  );
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const PermissionState = {
 | 
				
			||||||
 | 
					  Waiting: "waiting",
 | 
				
			||||||
 | 
					  Granted: "granted",
 | 
				
			||||||
 | 
					  Denied: "denied",
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function RoomSetupView({
 | 
				
			||||||
 | 
					  roomName,
 | 
				
			||||||
 | 
					  onInitLocalParticipant,
 | 
				
			||||||
 | 
					  onEnter,
 | 
				
			||||||
 | 
					  microphoneMuted,
 | 
				
			||||||
 | 
					  localVideoMuted,
 | 
				
			||||||
 | 
					  toggleLocalVideoMuted,
 | 
				
			||||||
 | 
					  toggleMicrophoneMuted,
 | 
				
			||||||
 | 
					  groupCall,
 | 
				
			||||||
 | 
					}) {
 | 
				
			||||||
 | 
					  const videoRef = useRef();
 | 
				
			||||||
 | 
					  const [permissionState, setPermissionState] = useState(
 | 
				
			||||||
 | 
					    PermissionState.Waiting
 | 
				
			||||||
 | 
					  );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  useEffect(() => {
 | 
				
			||||||
 | 
					    onInitLocalParticipant()
 | 
				
			||||||
 | 
					      .then((localParticipant) => {
 | 
				
			||||||
 | 
					        if (videoRef.current) {
 | 
				
			||||||
 | 
					          videoRef.current.srcObject = localParticipant.usermediaStream;
 | 
				
			||||||
 | 
					          videoRef.current.play();
 | 
				
			||||||
 | 
					          setPermissionState(PermissionState.Granted);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      })
 | 
				
			||||||
 | 
					      .catch(() => {
 | 
				
			||||||
 | 
					        if (videoRef.current) {
 | 
				
			||||||
 | 
					          setPermissionState(PermissionState.Denied);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
 | 
					  }, [onInitLocalParticipant]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  return (
 | 
					  return (
 | 
				
			||||||
    <>
 | 
					    <>
 | 
				
			||||||
      <Header>
 | 
					      <Header>
 | 
				
			||||||
        <LeftNav />
 | 
					        <LeftNav />
 | 
				
			||||||
 | 
					        <CenterNav>
 | 
				
			||||||
 | 
					          <h3>{roomName}</h3>
 | 
				
			||||||
 | 
					        </CenterNav>
 | 
				
			||||||
      </Header>
 | 
					      </Header>
 | 
				
			||||||
      <Content>
 | 
					      <div className={styles.joinRoom}>
 | 
				
			||||||
        <Center>
 | 
					        <div className={styles.preview}>
 | 
				
			||||||
          <Modal>
 | 
					          {permissionState === PermissionState.Denied && (
 | 
				
			||||||
            <h2>Login As Guest</h2>
 | 
					            <p className={styles.webcamPermissions}>
 | 
				
			||||||
            <form onSubmit={onSubmitLoginForm}>
 | 
					              Webcam permissions needed to join the call.
 | 
				
			||||||
              <FieldRow>
 | 
					            </p>
 | 
				
			||||||
                <InputField
 | 
					          )}
 | 
				
			||||||
                  type="text"
 | 
					          <video ref={videoRef} muted playsInline disablePictureInPicture />
 | 
				
			||||||
                  ref={displayNameRef}
 | 
					        </div>
 | 
				
			||||||
                  placeholder="Display Name"
 | 
					        {permissionState === PermissionState.Granted && (
 | 
				
			||||||
                  label="Display Name"
 | 
					          <div className={styles.previewButtons}>
 | 
				
			||||||
                  autoCorrect="off"
 | 
					            <MicButton
 | 
				
			||||||
                  autoCapitalize="none"
 | 
					              muted={microphoneMuted}
 | 
				
			||||||
                />
 | 
					              onClick={toggleMicrophoneMuted}
 | 
				
			||||||
              </FieldRow>
 | 
					            />
 | 
				
			||||||
              {error && (
 | 
					            <VideoButton
 | 
				
			||||||
                <FieldRow>
 | 
					              enabled={localVideoMuted}
 | 
				
			||||||
                  <ErrorMessage>{error.message}</ErrorMessage>
 | 
					              onClick={toggleLocalVideoMuted}
 | 
				
			||||||
                </FieldRow>
 | 
					            />
 | 
				
			||||||
              )}
 | 
					          </div>
 | 
				
			||||||
              <FieldRow rightAlign>
 | 
					        )}
 | 
				
			||||||
                <Button type="submit">Login as guest</Button>
 | 
					        <Button
 | 
				
			||||||
              </FieldRow>
 | 
					          disabled={permissionState !== PermissionState.Granted}
 | 
				
			||||||
            </form>
 | 
					          onClick={onEnter}
 | 
				
			||||||
            <Info>
 | 
					        >
 | 
				
			||||||
              <Link
 | 
					          Enter Call
 | 
				
			||||||
                to={{
 | 
					        </Button>
 | 
				
			||||||
                  pathname: "/login",
 | 
					      </div>
 | 
				
			||||||
                  state: location.state,
 | 
					 | 
				
			||||||
                }}
 | 
					 | 
				
			||||||
              >
 | 
					 | 
				
			||||||
                Sign in
 | 
					 | 
				
			||||||
              </Link>
 | 
					 | 
				
			||||||
              {" or "}
 | 
					 | 
				
			||||||
              <Link
 | 
					 | 
				
			||||||
                to={{
 | 
					 | 
				
			||||||
                  pathname: "/register",
 | 
					 | 
				
			||||||
                  state: location.state,
 | 
					 | 
				
			||||||
                }}
 | 
					 | 
				
			||||||
              >
 | 
					 | 
				
			||||||
                Create account
 | 
					 | 
				
			||||||
              </Link>
 | 
					 | 
				
			||||||
            </Info>
 | 
					 | 
				
			||||||
          </Modal>
 | 
					 | 
				
			||||||
        </Center>
 | 
					 | 
				
			||||||
      </Content>
 | 
					 | 
				
			||||||
    </>
 | 
					    </>
 | 
				
			||||||
  );
 | 
					  );
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
function JoinRoom({
 | 
					function InRoomView({
 | 
				
			||||||
  joining,
 | 
					  roomName,
 | 
				
			||||||
  joinCall,
 | 
					  microphoneMuted,
 | 
				
			||||||
  manager,
 | 
					  localVideoMuted,
 | 
				
			||||||
  toggleMuteVideo,
 | 
					  toggleLocalVideoMuted,
 | 
				
			||||||
  toggleMuteAudio,
 | 
					  toggleMicrophoneMuted,
 | 
				
			||||||
  videoMuted,
 | 
					  participants,
 | 
				
			||||||
  audioMuted,
 | 
					  onLeave,
 | 
				
			||||||
 | 
					  groupCall,
 | 
				
			||||||
}) {
 | 
					}) {
 | 
				
			||||||
  const videoRef = useRef();
 | 
					  const [debugMode, toggleDebugMode] = useDebugMode();
 | 
				
			||||||
  const [hasPermissions, setHasPermissions] = useState(false);
 | 
					  const [roomLayout, toggleRoomLayout] = useRoomLayout();
 | 
				
			||||||
  const [needsPermissions, setNeedsPermissions] = useState(false);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  useEffect(() => {
 | 
					 | 
				
			||||||
    manager
 | 
					 | 
				
			||||||
      .getLocalVideoStream()
 | 
					 | 
				
			||||||
      .then((stream) => {
 | 
					 | 
				
			||||||
        if (videoRef.current) {
 | 
					 | 
				
			||||||
          videoRef.current.srcObject = stream;
 | 
					 | 
				
			||||||
          videoRef.current.play();
 | 
					 | 
				
			||||||
          setHasPermissions(true);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
      })
 | 
					 | 
				
			||||||
      .catch(() => {
 | 
					 | 
				
			||||||
        if (videoRef.current) {
 | 
					 | 
				
			||||||
          setNeedsPermissions(true);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
      });
 | 
					 | 
				
			||||||
  }, [manager]);
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
  return (
 | 
					  return (
 | 
				
			||||||
    <div className={styles.joinRoom}>
 | 
					    <>
 | 
				
			||||||
      <div className={styles.preview}>
 | 
					      <Header>
 | 
				
			||||||
        {needsPermissions && (
 | 
					        <LeftNav />
 | 
				
			||||||
          <p className={styles.webcamPermissions}>
 | 
					        <CenterNav>
 | 
				
			||||||
            Webcam permissions needed to join the call.
 | 
					          <h3>{roomName}</h3>
 | 
				
			||||||
          </p>
 | 
					        </CenterNav>
 | 
				
			||||||
        )}
 | 
					        <RightNav>
 | 
				
			||||||
        <video ref={videoRef} muted playsInline disablePictureInPicture />
 | 
					          <LayoutToggleButton
 | 
				
			||||||
      </div>
 | 
					            title={roomLayout === "spotlight" ? "Spotlight" : "Gallery"}
 | 
				
			||||||
      {hasPermissions && (
 | 
					            layout={roomLayout}
 | 
				
			||||||
        <div className={styles.previewButtons}>
 | 
					            onClick={toggleRoomLayout}
 | 
				
			||||||
          <MicButton muted={audioMuted} onClick={toggleMuteAudio} />
 | 
					          />
 | 
				
			||||||
          <VideoButton enabled={videoMuted} onClick={toggleMuteVideo} />
 | 
					          <SettingsButton
 | 
				
			||||||
 | 
					            title={debugMode ? "Disable DevTools" : "Enable DevTools"}
 | 
				
			||||||
 | 
					            onClick={toggleDebugMode}
 | 
				
			||||||
 | 
					          />
 | 
				
			||||||
 | 
					        </RightNav>
 | 
				
			||||||
 | 
					      </Header>
 | 
				
			||||||
 | 
					      {participants.length === 0 ? (
 | 
				
			||||||
 | 
					        <div className={styles.centerMessage}>
 | 
				
			||||||
 | 
					          <p>Waiting for other participants...</p>
 | 
				
			||||||
        </div>
 | 
					        </div>
 | 
				
			||||||
 | 
					      ) : (
 | 
				
			||||||
 | 
					        <VideoGrid participants={participants} layout={roomLayout} />
 | 
				
			||||||
      )}
 | 
					      )}
 | 
				
			||||||
      <Button disabled={!hasPermissions || joining} onClick={joinCall}>
 | 
					      <div className={styles.footer}>
 | 
				
			||||||
        Join Call
 | 
					        <MicButton muted={microphoneMuted} onClick={toggleMicrophoneMuted} />
 | 
				
			||||||
      </Button>
 | 
					        <VideoButton
 | 
				
			||||||
    </div>
 | 
					          enabled={localVideoMuted}
 | 
				
			||||||
 | 
					          onClick={toggleLocalVideoMuted}
 | 
				
			||||||
 | 
					        />
 | 
				
			||||||
 | 
					        <HangupButton onClick={onLeave} />
 | 
				
			||||||
 | 
					      </div>
 | 
				
			||||||
 | 
					      {debugMode && <DevTools groupCall={groupCall} />}
 | 
				
			||||||
 | 
					    </>
 | 
				
			||||||
  );
 | 
					  );
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -422,7 +422,7 @@ export function VideoGrid({ participants, layout }) {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      for (const tile of tiles) {
 | 
					      for (const tile of tiles) {
 | 
				
			||||||
        let participant = participants.find(
 | 
					        let participant = participants.find(
 | 
				
			||||||
          (participant) => participant.userId === tile.key
 | 
					          (participant) => participant.member.userId === tile.key
 | 
				
			||||||
        );
 | 
					        );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        let remove = false;
 | 
					        let remove = false;
 | 
				
			||||||
| 
						 | 
					@ -442,7 +442,7 @@ export function VideoGrid({ participants, layout }) {
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        newTiles.push({
 | 
					        newTiles.push({
 | 
				
			||||||
          key: participant.userId,
 | 
					          key: participant.member.userId,
 | 
				
			||||||
          participant,
 | 
					          participant,
 | 
				
			||||||
          remove,
 | 
					          remove,
 | 
				
			||||||
          presenter,
 | 
					          presenter,
 | 
				
			||||||
| 
						 | 
					@ -450,13 +450,13 @@ export function VideoGrid({ participants, layout }) {
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      for (const participant of participants) {
 | 
					      for (const participant of participants) {
 | 
				
			||||||
        if (newTiles.some(({ key }) => participant.userId === key)) {
 | 
					        if (newTiles.some(({ key }) => participant.member.userId === key)) {
 | 
				
			||||||
          continue;
 | 
					          continue;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        // Added tiles
 | 
					        // Added tiles
 | 
				
			||||||
        newTiles.push({
 | 
					        newTiles.push({
 | 
				
			||||||
          key: participant.userId,
 | 
					          key: participant.member.userId,
 | 
				
			||||||
          participant,
 | 
					          participant,
 | 
				
			||||||
          remove: false,
 | 
					          remove: false,
 | 
				
			||||||
          presenter: layout === "spotlight" && participant.activeSpeaker,
 | 
					          presenter: layout === "spotlight" && participant.activeSpeaker,
 | 
				
			||||||
| 
						 | 
					@ -719,17 +719,19 @@ function ParticipantTile({ style, participant, remove, presenter, ...rest }) {
 | 
				
			||||||
  const videoRef = useRef();
 | 
					  const videoRef = useRef();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  useEffect(() => {
 | 
					  useEffect(() => {
 | 
				
			||||||
    if (participant.stream) {
 | 
					    if (participant.usermediaStream) {
 | 
				
			||||||
      if (participant.local) {
 | 
					      // Mute the local video
 | 
				
			||||||
 | 
					      // TODO: Should GroupCallParticipant have a local field?
 | 
				
			||||||
 | 
					      if (!participant.call) {
 | 
				
			||||||
        videoRef.current.muted = true;
 | 
					        videoRef.current.muted = true;
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      videoRef.current.srcObject = participant.stream;
 | 
					      videoRef.current.srcObject = participant.usermediaStream;
 | 
				
			||||||
      videoRef.current.play();
 | 
					      videoRef.current.play();
 | 
				
			||||||
    } else {
 | 
					    } else {
 | 
				
			||||||
      videoRef.current.srcObject = null;
 | 
					      videoRef.current.srcObject = null;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  }, [participant.stream]);
 | 
					  }, [participant.usermediaStream]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  // Firefox doesn't respect the disablePictureInPicture attribute
 | 
					  // Firefox doesn't respect the disablePictureInPicture attribute
 | 
				
			||||||
  // https://bugzilla.mozilla.org/show_bug.cgi?id=1611831
 | 
					  // https://bugzilla.mozilla.org/show_bug.cgi?id=1611831
 | 
				
			||||||
| 
						 | 
					@ -738,15 +740,15 @@ function ParticipantTile({ style, participant, remove, presenter, ...rest }) {
 | 
				
			||||||
    <animated.div className={styles.participantTile} style={style} {...rest}>
 | 
					    <animated.div className={styles.participantTile} style={style} {...rest}>
 | 
				
			||||||
      <div
 | 
					      <div
 | 
				
			||||||
        className={classNames(styles.participantName, {
 | 
					        className={classNames(styles.participantName, {
 | 
				
			||||||
          [styles.speaking]: participant.speaking,
 | 
					          [styles.speaking]: participant.usermediaStream?.speaking,
 | 
				
			||||||
        })}
 | 
					        })}
 | 
				
			||||||
      >
 | 
					      >
 | 
				
			||||||
        {participant.speaking ? (
 | 
					        {participant.usermediaStream?.speaking ? (
 | 
				
			||||||
          <MicIcon />
 | 
					          <MicIcon />
 | 
				
			||||||
        ) : participant.audioMuted ? (
 | 
					        ) : participant.isAudioMuted() ? (
 | 
				
			||||||
          <MuteMicIcon className={styles.muteMicIcon} />
 | 
					          <MuteMicIcon className={styles.muteMicIcon} />
 | 
				
			||||||
        ) : null}
 | 
					        ) : null}
 | 
				
			||||||
        <span>{participant.displayName}</span>
 | 
					        <span>{participant.member.rawDisplayName}</span>
 | 
				
			||||||
      </div>
 | 
					      </div>
 | 
				
			||||||
      {participant.videoMuted && (
 | 
					      {participant.videoMuted && (
 | 
				
			||||||
        <DisableVideoIcon
 | 
					        <DisableVideoIcon
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue