feat(prejoin) Expose prejoin app
This commit is contained in:
parent
f376542441
commit
0e5091adba
|
@ -1,17 +1,21 @@
|
|||
/**
|
||||
* Shared style for full screen local track based dialogs/modals.
|
||||
*/
|
||||
.premeeting-screen {
|
||||
align-items: stretch;
|
||||
background: #1C2025;
|
||||
.premeeting-screen,
|
||||
.preview-overlay {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
right: 0;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
}
|
||||
|
||||
.premeeting-screen {
|
||||
align-items: stretch;
|
||||
background: radial-gradient(50% 50% at 50% 50%, #5D95C7 0%, #376288 100%), #FFFFFF;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
font-size: 1.3em;
|
||||
left: 0;
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: 0;
|
||||
z-index: $toolbarZ + 1;
|
||||
|
||||
.action-btn {
|
||||
|
@ -74,9 +78,13 @@
|
|||
}
|
||||
}
|
||||
|
||||
.preview-overlay {
|
||||
background-image: linear-gradient(transparent, black);
|
||||
z-index: $toolbarZ + 1;
|
||||
}
|
||||
|
||||
.content {
|
||||
align-items: center;
|
||||
background-image: linear-gradient(transparent, black);
|
||||
display: flex;
|
||||
flex: 1;
|
||||
flex-direction: column;
|
||||
|
|
10
index.html
10
index.html
|
@ -8,7 +8,17 @@
|
|||
|
||||
<link rel="apple-touch-icon" href="images/apple-touch-icon.png">
|
||||
<link rel="stylesheet" href="css/all.css">
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
if (!JitsiMeetJS.app) {
|
||||
return;
|
||||
}
|
||||
|
||||
JitsiMeetJS.app.renderEntryPoint({
|
||||
Component: JitsiMeetJS.app.entryPoints.APP
|
||||
})
|
||||
})
|
||||
</script>
|
||||
<script>
|
||||
// IE11 and earlier can be identified via their user agent and be
|
||||
// redirected to a page that is known to have no newer js syntax.
|
||||
|
|
|
@ -122,14 +122,14 @@ export default class BaseApp extends Component<*, State> {
|
|||
* @returns {ReactElement}
|
||||
*/
|
||||
render() {
|
||||
const { route: { component }, store } = this.state;
|
||||
const { route: { component, props }, store } = this.state;
|
||||
|
||||
if (store) {
|
||||
return (
|
||||
<I18nextProvider i18n = { i18next }>
|
||||
<Provider store = { store }>
|
||||
<Fragment>
|
||||
{ this._createMainElement(component) }
|
||||
{ this._createMainElement(component, props) }
|
||||
<SoundCollection />
|
||||
{ this._createExtraElement() }
|
||||
{ this._renderDialogContainer() }
|
||||
|
|
|
@ -24,6 +24,16 @@ type Props = {
|
|||
*/
|
||||
name?: string,
|
||||
|
||||
/**
|
||||
* Indicates whether the avatar should be shown when video is off
|
||||
*/
|
||||
showAvatar: boolean,
|
||||
|
||||
/**
|
||||
* Indicates whether the label and copy url action should be shown
|
||||
*/
|
||||
showConferenceInfo: boolean,
|
||||
|
||||
/**
|
||||
* Title of the screen.
|
||||
*/
|
||||
|
@ -45,13 +55,23 @@ type Props = {
|
|||
* on the prejoin screen (pre-connection) or lobby (post-connection).
|
||||
*/
|
||||
export default class PreMeetingScreen extends PureComponent<Props> {
|
||||
/**
|
||||
* Default values for {@code Prejoin} component's properties.
|
||||
*
|
||||
* @static
|
||||
*/
|
||||
static defaultProps = {
|
||||
showAvatar: true,
|
||||
showConferenceInfo: true
|
||||
};
|
||||
|
||||
/**
|
||||
* Implements {@code PureComponent#render}.
|
||||
*
|
||||
* @inheritdoc
|
||||
*/
|
||||
render() {
|
||||
const { name, title, videoMuted, videoTrack } = this.props;
|
||||
const { name, showAvatar, showConferenceInfo, title, videoMuted, videoTrack } = this.props;
|
||||
|
||||
return (
|
||||
<div
|
||||
|
@ -59,13 +79,19 @@ export default class PreMeetingScreen extends PureComponent<Props> {
|
|||
id = 'lobby-screen'>
|
||||
<Preview
|
||||
name = { name }
|
||||
showAvatar = { showAvatar }
|
||||
videoMuted = { videoMuted }
|
||||
videoTrack = { videoTrack } />
|
||||
{!videoMuted && <div className = 'preview-overlay' />}
|
||||
<div className = 'content'>
|
||||
{showConferenceInfo && (
|
||||
<>
|
||||
<div className = 'title'>
|
||||
{ title }
|
||||
</div>
|
||||
<CopyMeetingUrl />
|
||||
</>
|
||||
)}
|
||||
{ this.props.children }
|
||||
<div className = 'media-btn-container'>
|
||||
<AudioSettingsButton visible = { true } />
|
||||
|
|
|
@ -14,6 +14,11 @@ export type Props = {
|
|||
*/
|
||||
name: string,
|
||||
|
||||
/**
|
||||
* Indicates whether the avatar should be shown when video is off
|
||||
*/
|
||||
showAvatar: boolean,
|
||||
|
||||
/**
|
||||
* Flag signaling the visibility of camera preview.
|
||||
*/
|
||||
|
@ -32,7 +37,7 @@ export type Props = {
|
|||
* @returns {ReactElement}
|
||||
*/
|
||||
function Preview(props: Props) {
|
||||
const { name, videoMuted, videoTrack } = props;
|
||||
const { name, showAvatar, videoMuted, videoTrack } = props;
|
||||
|
||||
if (!videoMuted && videoTrack) {
|
||||
return (
|
||||
|
@ -44,6 +49,7 @@ function Preview(props: Props) {
|
|||
);
|
||||
}
|
||||
|
||||
if (showAvatar) {
|
||||
return (
|
||||
<div
|
||||
className = 'no-video'
|
||||
|
@ -55,8 +61,15 @@ function Preview(props: Props) {
|
|||
size = { 200 } />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
Preview.defaultProps = {
|
||||
showAvatar: true
|
||||
};
|
||||
|
||||
/**
|
||||
* Maps part of the Redux state to the props of this component.
|
||||
*
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
/* global APP */
|
||||
|
||||
import JitsiMeetJS, { JitsiTrackErrors, browser } from '../lib-jitsi-meet';
|
||||
import { MEDIA_TYPE } from '../media';
|
||||
import { MEDIA_TYPE, setAudioMuted } from '../media';
|
||||
import {
|
||||
getUserSelectedCameraDeviceId,
|
||||
getUserSelectedMicDeviceId
|
||||
|
@ -125,6 +125,89 @@ export function createLocalTracksF(options = {}, firePermissionPromptIsShownEven
|
|||
}));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an object containing a promise which resolves with the created tracks &
|
||||
* the errors resulting from that process.
|
||||
*
|
||||
* @returns {Promise<JitsiLocalTrack>}
|
||||
*
|
||||
* @todo Refactor to not use APP
|
||||
*/
|
||||
export function createPrejoinTracks() {
|
||||
const errors = {};
|
||||
const initialDevices = [ 'audio' ];
|
||||
const requestedAudio = true;
|
||||
let requestedVideo = false;
|
||||
const { startAudioOnly, startWithAudioMuted, startWithVideoMuted } = APP.store.getState()['features/base/settings'];
|
||||
|
||||
// Always get a handle on the audio input device so that we have statistics even if the user joins the
|
||||
// conference muted. Previous implementation would only acquire the handle when the user first unmuted,
|
||||
// which would results in statistics ( such as "No audio input" or "Are you trying to speak?") being available
|
||||
// only after that point.
|
||||
if (startWithAudioMuted) {
|
||||
APP.store.dispatch(setAudioMuted(true));
|
||||
}
|
||||
|
||||
if (!startWithVideoMuted && !startAudioOnly) {
|
||||
initialDevices.push('video');
|
||||
requestedVideo = true;
|
||||
}
|
||||
|
||||
let tryCreateLocalTracks;
|
||||
|
||||
if (!requestedAudio && !requestedVideo) {
|
||||
// Resolve with no tracks
|
||||
tryCreateLocalTracks = Promise.resolve([]);
|
||||
} else {
|
||||
tryCreateLocalTracks = createLocalTracksF({ devices: initialDevices }, true)
|
||||
.catch(err => {
|
||||
if (requestedAudio && requestedVideo) {
|
||||
|
||||
// Try audio only...
|
||||
errors.audioAndVideoError = err;
|
||||
|
||||
return (
|
||||
createLocalTracksF({ devices: [ 'audio' ] }, true));
|
||||
} else if (requestedAudio && !requestedVideo) {
|
||||
errors.audioOnlyError = err;
|
||||
|
||||
return [];
|
||||
} else if (requestedVideo && !requestedAudio) {
|
||||
errors.videoOnlyError = err;
|
||||
|
||||
return [];
|
||||
}
|
||||
logger.error('Should never happen');
|
||||
})
|
||||
.catch(err => {
|
||||
// Log this just in case...
|
||||
if (!requestedAudio) {
|
||||
logger.error('The impossible just happened', err);
|
||||
}
|
||||
errors.audioOnlyError = err;
|
||||
|
||||
// Try video only...
|
||||
return requestedVideo
|
||||
? createLocalTracksF({ devices: [ 'video' ] }, true)
|
||||
: [];
|
||||
})
|
||||
.catch(err => {
|
||||
// Log this just in case...
|
||||
if (!requestedVideo) {
|
||||
logger.error('The impossible just happened', err);
|
||||
}
|
||||
errors.videoOnlyError = err;
|
||||
|
||||
return [];
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
tryCreateLocalTracks,
|
||||
errors
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns local audio track.
|
||||
*
|
||||
|
|
|
@ -143,6 +143,10 @@ MiddlewareRegistry.register(store => next => action => {
|
|||
if (typeof APP !== 'undefined') {
|
||||
const result = next(action);
|
||||
|
||||
if (isPrejoinPageVisible(store.getState())) {
|
||||
return result;
|
||||
}
|
||||
|
||||
const { jitsiTrack } = action.track;
|
||||
const muted = jitsiTrack.isMuted();
|
||||
const participantID = jitsiTrack.getParticipantId();
|
||||
|
|
|
@ -79,11 +79,21 @@ type Props = {
|
|||
*/
|
||||
setJoinByPhoneDialogVisiblity: Function,
|
||||
|
||||
/**
|
||||
* Indicates whether the avatar should be shown when video is off
|
||||
*/
|
||||
showAvatar: boolean,
|
||||
|
||||
/**
|
||||
* Flag signaling the visibility of camera preview.
|
||||
*/
|
||||
showCameraPreview: boolean,
|
||||
|
||||
/**
|
||||
* Flag signaling the visibility of join label, input and buttons
|
||||
*/
|
||||
showJoinActions: boolean,
|
||||
|
||||
/**
|
||||
* If 'JoinByPhoneDialog' is visible or not.
|
||||
*/
|
||||
|
@ -112,6 +122,15 @@ type State = {
|
|||
* This component is displayed before joining a meeting.
|
||||
*/
|
||||
class Prejoin extends Component<Props, State> {
|
||||
/**
|
||||
* Default values for {@code Prejoin} component's properties.
|
||||
*
|
||||
* @static
|
||||
*/
|
||||
static defaultProps = {
|
||||
showJoinActions: true
|
||||
};
|
||||
|
||||
/**
|
||||
* Initializes a new {@code Prejoin} instance.
|
||||
*
|
||||
|
@ -223,8 +242,10 @@ class Prejoin extends Component<Props, State> {
|
|||
joinConference,
|
||||
joinConferenceWithoutAudio,
|
||||
name,
|
||||
showAvatar,
|
||||
showCameraPreview,
|
||||
showDialog,
|
||||
showJoinActions,
|
||||
t,
|
||||
videoTrack
|
||||
} = this.props;
|
||||
|
@ -236,9 +257,12 @@ class Prejoin extends Component<Props, State> {
|
|||
<PreMeetingScreen
|
||||
footer = { this._renderFooter() }
|
||||
name = { name }
|
||||
showAvatar = { showAvatar }
|
||||
showConferenceInfo = { showJoinActions }
|
||||
title = { t('prejoin.joinMeeting') }
|
||||
videoMuted = { !showCameraPreview }
|
||||
videoTrack = { videoTrack }>
|
||||
{showJoinActions && (
|
||||
<div className = 'prejoin-input-area-container'>
|
||||
<div className = 'prejoin-input-area'>
|
||||
<InputField
|
||||
|
@ -291,6 +315,7 @@ class Prejoin extends Component<Props, State> {
|
|||
<span>{t('prejoin.doNotShow')}</span>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{ showDialog && (
|
||||
<JoinByPhoneDialog
|
||||
joinConferenceWithoutAudio = { joinConferenceWithoutAudio }
|
||||
|
|
|
@ -0,0 +1,93 @@
|
|||
// @flow
|
||||
|
||||
import { AtlasKitThemeProvider } from '@atlaskit/theme';
|
||||
import React from 'react';
|
||||
|
||||
import { BaseApp } from '../../../features/base/app';
|
||||
import { setConfig } from '../../base/config';
|
||||
import { createPrejoinTracks } from '../../base/tracks';
|
||||
import { initPrejoin } from '../actions';
|
||||
|
||||
import Prejoin from './Prejoin';
|
||||
|
||||
type Props = {
|
||||
|
||||
/**
|
||||
* Indicates whether the avatar should be shown when video is off
|
||||
*/
|
||||
showAvatar: boolean,
|
||||
|
||||
/**
|
||||
* Flag signaling the visibility of join label, input and buttons
|
||||
*/
|
||||
showJoinActions: boolean,
|
||||
};
|
||||
|
||||
/**
|
||||
* Wrapper application for prejoin.
|
||||
*
|
||||
* @extends BaseApp
|
||||
*/
|
||||
export default class PrejoinApp extends BaseApp<Props> {
|
||||
_init: Promise<*>;
|
||||
|
||||
/**
|
||||
* Navigates to {@link Prejoin} upon mount.
|
||||
*
|
||||
* @returns {void}
|
||||
*/
|
||||
componentDidMount() {
|
||||
super.componentDidMount();
|
||||
|
||||
this._init.then(async () => {
|
||||
const { store } = this.state;
|
||||
const { dispatch } = store;
|
||||
const { showAvatar, showJoinActions } = this.props;
|
||||
|
||||
super._navigate({
|
||||
component: Prejoin,
|
||||
props: {
|
||||
showAvatar,
|
||||
showJoinActions
|
||||
}
|
||||
});
|
||||
|
||||
const { startWithAudioMuted, startWithVideoMuted } = store.getState()['features/base/settings'];
|
||||
|
||||
dispatch(setConfig({
|
||||
prejoinPageEnabled: true,
|
||||
startWithAudioMuted,
|
||||
startWithVideoMuted
|
||||
}));
|
||||
|
||||
const { tryCreateLocalTracks, errors } = createPrejoinTracks();
|
||||
|
||||
const tracks = await tryCreateLocalTracks;
|
||||
|
||||
dispatch(initPrejoin(tracks, errors));
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Overrides the parent method to inject {@link AtlasKitThemeProvider} as
|
||||
* the top most component.
|
||||
*
|
||||
* @override
|
||||
*/
|
||||
_createMainElement(component, props) {
|
||||
return (
|
||||
<AtlasKitThemeProvider mode = 'dark'>
|
||||
{ super._createMainElement(component, props) }
|
||||
</AtlasKitThemeProvider>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders the platform specific dialog container.
|
||||
*
|
||||
* @returns {React$Element}
|
||||
*/
|
||||
_renderDialogContainer() {
|
||||
return null;
|
||||
}
|
||||
}
|
|
@ -8,6 +8,8 @@ import { getJitsiMeetTransport } from '../modules/transport';
|
|||
import { App } from './features/app/components';
|
||||
import { getLogger } from './features/base/logging/functions';
|
||||
import { Platform } from './features/base/react';
|
||||
import { getJitsiMeetGlobalNS } from './features/base/util';
|
||||
import PrejoinApp from './features/prejoin/components/PrejoinApp';
|
||||
|
||||
const logger = getLogger('index.web');
|
||||
const OS = Platform.OS;
|
||||
|
@ -20,9 +22,6 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||
|
||||
APP.connectionTimes['document.ready'] = now;
|
||||
logger.log('(TIME) document ready:\t', now);
|
||||
|
||||
// Render the main/root Component.
|
||||
ReactDOM.render(<App />, document.getElementById('react'));
|
||||
});
|
||||
|
||||
// Workaround for the issue when returning to a page with the back button and
|
||||
|
@ -56,3 +55,21 @@ window.addEventListener('beforeunload', () => {
|
|||
APP.API.dispose();
|
||||
getJitsiMeetTransport().dispose();
|
||||
});
|
||||
|
||||
const globalNS = getJitsiMeetGlobalNS();
|
||||
|
||||
globalNS.entryPoints = {
|
||||
APP: App,
|
||||
PREJOIN: PrejoinApp
|
||||
};
|
||||
|
||||
globalNS.renderEntryPoint = ({
|
||||
Component,
|
||||
props = {},
|
||||
elementId = 'react'
|
||||
}) => {
|
||||
ReactDOM.render(
|
||||
<Component { ...props } />,
|
||||
document.getElementById(elementId)
|
||||
);
|
||||
};
|
||||
|
|
|
@ -0,0 +1,41 @@
|
|||
<html xmlns="http://www.w3.org/1999/html">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta http-equiv="content-type" content="text/html;charset=utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<!--#include virtual="/base.html" -->
|
||||
<link rel="stylesheet" href="../css/all.css">
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
if (!JitsiMeetJS.app) {
|
||||
return;
|
||||
}
|
||||
|
||||
const url = new URL(window.location.href);
|
||||
const params = new URLSearchParams(url.search);
|
||||
const showAvatar = params.get('showAvatar') === 'true';
|
||||
const showJoinActions = params.get('showJoinActions') === 'true';
|
||||
const css = params.get('style');
|
||||
const style = document.createElement('style');
|
||||
style.appendChild(document.createTextNode(css));
|
||||
document.head.appendChild(style);
|
||||
|
||||
JitsiMeetJS.app.renderEntryPoint({
|
||||
Component: JitsiMeetJS.app.entryPoints.PREJOIN,
|
||||
props: {
|
||||
showAvatar,
|
||||
showJoinActions
|
||||
}
|
||||
})
|
||||
})
|
||||
</script>
|
||||
<!--#include virtual="/title.html" -->
|
||||
<script>var config = {}</script><!-- adapt to your needs, i.e. set hosts and bosh path -->
|
||||
<script>var interfaceConfig = {}</script>
|
||||
<script src="../libs/lib-jitsi-meet.min.js?v=139"></script>
|
||||
<script src="../libs/app.bundle.min.js?v=139"></script>
|
||||
</head>
|
||||
<body>
|
||||
<div id="react"></div>
|
||||
</body>
|
||||
</html>
|
Loading…
Reference in New Issue