Fix fullscreen edge cases

This commit is contained in:
Vitor Mateus 2019-07-26 13:48:51 -03:00
parent 6ec4fd0263
commit e8f15b97b6
17 changed files with 135 additions and 81 deletions

View File

@ -1,11 +1,11 @@
import React from 'react';
import FullscreenButtonComponent from './component';
import toggleFullScreen from '/imports/ui/components/nav-bar/settings-dropdown/service';
import FullscreenService from './service';
const FullscreenButtonContainer = props => <FullscreenButtonComponent {...props} />;
export default (props) => {
const handleToggleFullScreen = ref => toggleFullScreen(ref);
const handleToggleFullScreen = ref => FullscreenService.toggleFullScreen(ref);
const isIphone = navigator.userAgent.match(/iPhone/i);
return (
<FullscreenButtonContainer {...props} {...{ handleToggleFullScreen, isIphone }} />

View File

@ -0,0 +1,52 @@
function getFullscreenElement() {
if (document.fullscreenElement) return document.fullscreenElement;
if (document.webkitFullscreenElement) return document.webkitFullscreenElement;
if (document.mozFullScreenElement) return document.mozFullScreenElement;
if (document.msFullscreenElement) return document.msFullscreenElement;
return null;
}
const isFullScreen = (element) => {
if (getFullscreenElement() && getFullscreenElement() === element) {
return true;
}
return false;
};
function cancelFullScreen() {
if (document.exitFullscreen) {
document.exitFullscreen();
} else if (document.mozCancelFullScreen) {
document.mozCancelFullScreen();
} else if (document.webkitExitFullscreen) {
document.webkitExitFullscreen();
}
}
function fullscreenRequest(element) {
if (element.requestFullscreen) {
element.requestFullscreen();
} else if (element.mozRequestFullScreen) {
element.mozRequestFullScreen();
} else if (element.webkitRequestFullscreen) {
element.webkitRequestFullscreen();
element.webkitRequestFullScreen(Element.ALLOW_KEYBOARD_INPUT);
} else if (element.msRequestFullscreen) {
element.msRequestFullscreen();
}
}
const toggleFullScreen = (ref = null) => {
const element = ref || document.documentElement;
if (isFullScreen(element)) {
cancelFullScreen();
} else {
fullscreenRequest(element);
}
};
export default {
toggleFullScreen,
isFullScreen,
};

View File

@ -65,7 +65,6 @@ export default class Media extends Component {
children,
audioModalIsOpen,
usersVideo,
isFullscreen,
} = this.props;
const contentClassName = cx({
@ -101,7 +100,6 @@ export default class Media extends Component {
disableVideo={disableVideo}
audioModalIsOpen={audioModalIsOpen}
usersVideo={usersVideo}
isFullscreen={isFullscreen}
/>
</div>
);

View File

@ -122,9 +122,6 @@ export default withModalMounter(withTracker(() => {
data.children = <ScreenshareContainer />;
}
const isFullscreen = Session.get('isFullscreen');
data.isFullscreen = isFullscreen;
const usersVideo = VideoService.getAllUsersVideo();
data.usersVideo = usersVideo;

View File

@ -24,6 +24,7 @@ const initialState = {
dragging: false,
videoRef: null,
videoListRef: null,
isFullscreen: false,
};
const reducer = (state, action) => {
@ -112,6 +113,12 @@ const reducer = (state, action) => {
dragging: false,
};
}
case 'setIsFullscreen': {
return {
...state,
isFullscreen: action.value,
};
}
default: {
throw new Error('Unexpected action');
}

View File

@ -16,6 +16,7 @@ import DropdownListItem from '/imports/ui/components/dropdown/list/item/componen
import DropdownListSeparator from '/imports/ui/components/dropdown/list/separator/component';
import ShortcutHelpComponent from '/imports/ui/components/shortcut-help/component';
import withShortcutHelper from '/imports/ui/components/shortcut-help/service';
import FullscreenService from '../../fullscreen-button/service';
import { styles } from '../styles';
@ -94,7 +95,6 @@ const propTypes = {
intl: intlShape.isRequired,
handleToggleFullscreen: PropTypes.func.isRequired,
mountModal: PropTypes.func.isRequired,
isFullscreen: PropTypes.bool,
noIOSFullscreen: PropTypes.bool,
amIModerator: PropTypes.bool,
shortcuts: PropTypes.string,
@ -103,7 +103,6 @@ const propTypes = {
};
const defaultProps = {
isFullscreen: false,
noIOSFullscreen: true,
amIModerator: false,
shortcuts: '',
@ -118,6 +117,7 @@ class SettingsDropdown extends PureComponent {
this.state = {
isSettingOpen: false,
isFullscreen: false,
};
// Set the logout code to 680 because it's not a real code and can be matched on the other side
@ -126,6 +126,11 @@ class SettingsDropdown extends PureComponent {
this.onActionsShow = this.onActionsShow.bind(this);
this.onActionsHide = this.onActionsHide.bind(this);
this.leaveSession = this.leaveSession.bind(this);
this.onFullscreenChange = this.onFullscreenChange.bind(this);
}
componentDidMount() {
document.addEventListener('fullscreenchange', () => this.onFullscreenChange());
}
onActionsShow() {
@ -140,13 +145,21 @@ class SettingsDropdown extends PureComponent {
});
}
onFullscreenChange() {
const { isFullscreen } = this.state;
const newIsFullscreen = FullscreenService.isFullScreen(document.documentElement);
if (isFullscreen !== newIsFullscreen) {
this.setState({ isFullscreen: newIsFullscreen });
}
}
getFullscreenItem() {
const {
intl,
isFullscreen,
noIOSFullscreen,
handleToggleFullscreen,
} = this.props;
const { isFullscreen } = this.state;
if (noIOSFullscreen || !ALLOW_FULLSCREEN) return null;

View File

@ -2,15 +2,14 @@ import React from 'react';
import { withTracker } from 'meteor/react-meteor-data';
import browser from 'browser-detect';
import SettingsDropdown from './component';
import toggleFullScreen from './service';
import FullscreenService from '../../fullscreen-button/service';
const SettingsDropdownContainer = props => (
<SettingsDropdown {...props} />
);
export default withTracker((props) => {
const isFullscreen = Session.get('isFullscreen');
const handleToggleFullscreen = () => toggleFullScreen();
const handleToggleFullscreen = () => FullscreenService.toggleFullScreen();
const BROWSER_RESULTS = browser();
const isSafari = BROWSER_RESULTS.name === 'safari';
const isIphone = navigator.userAgent.match(/iPhone/i);
@ -18,7 +17,6 @@ export default withTracker((props) => {
return {
amIModerator: props.amIModerator,
handleToggleFullscreen,
isFullscreen,
noIOSFullscreen,
isMeteorConnected: Meteor.status().connected,
};

View File

@ -1,29 +0,0 @@
const toggleFullScreen = (ref = null) => {
const element = ref || document.documentElement;
if (document.fullscreenElement
|| document.webkitFullscreenElement
|| document.mozFullScreenElement
|| document.msFullscreenElement) {
if (document.exitFullscreen) {
document.exitFullscreen();
} else if (document.mozCancelFullScreen) {
document.mozCancelFullScreen();
} else if (document.webkitExitFullscreen) {
document.webkitExitFullscreen();
}
// If the page is not currently fullscreen, make fullscreen
} else if (element.requestFullscreen) {
element.requestFullscreen();
} else if (element.mozRequestFullScreen) {
element.mozRequestFullScreen();
} else if (element.webkitRequestFullscreen) {
element.webkitRequestFullscreen();
element.webkitRequestFullScreen(Element.ALLOW_KEYBOARD_INPUT);
} else if (element.msRequestFullscreen) {
element.msRequestFullscreen();
}
};
export default toggleFullScreen;

View File

@ -14,6 +14,7 @@ import { styles } from './styles.scss';
import MediaService, { shouldEnableSwapLayout } from '../media/service';
import PresentationCloseButton from './presentation-close-button/component';
import DownloadPresentationButton from './download-presentation-button/component';
import FullscreenService from '../fullscreen-button/service';
import FullscreenButtonContainer from '../fullscreen-button/container';
const intlMessages = defineMessages({
@ -44,6 +45,7 @@ class PresentationArea extends PureComponent {
y: 0,
},
fitToWidth: false,
isFullscreen: false,
};
this.getSvgRef = this.getSvgRef.bind(this);
@ -52,6 +54,7 @@ class PresentationArea extends PureComponent {
this.touchUpdate = this.touchUpdate.bind(this);
this.pointUpdate = this.pointUpdate.bind(this);
this.fitToWidthHandler = this.fitToWidthHandler.bind(this);
this.onFullscreenChange = this.onFullscreenChange.bind(this);
}
componentDidMount() {
@ -61,6 +64,7 @@ class PresentationArea extends PureComponent {
});
this.getInitialPresentationSizes();
document.addEventListener('fullscreenchange', () => this.onFullscreenChange());
}
componentDidUpdate(prevProps, prevState) {
@ -91,6 +95,15 @@ class PresentationArea extends PureComponent {
});
}
onFullscreenChange() {
const { isFullscreen } = this.state;
const newIsFullscreen = FullscreenService.isFullScreen(this.refPresentationContainer);
if (isFullscreen !== newIsFullscreen) {
this.setState({ isFullscreen: newIsFullscreen });
window.dispatchEvent(new Event('resize'));
}
}
// returns a ref to the svg element, which is required by a WhiteboardOverlay
// to transform screen coordinates to svg coordinate system
getSvgRef() {
@ -271,7 +284,7 @@ class PresentationArea extends PureComponent {
}
renderPresentationClose() {
const { isFullscreen } = this.props;
const { isFullscreen } = this.state;
if (!shouldEnableSwapLayout() || isFullscreen) {
return null;
}
@ -464,10 +477,9 @@ class PresentationArea extends PureComponent {
const {
currentSlide,
podId,
isFullscreen,
} = this.props;
const { zoom, fitToWidth } = this.state;
const { zoom, fitToWidth, isFullscreen } = this.state;
if (!currentSlide) {
return null;
@ -524,8 +536,8 @@ class PresentationArea extends PureComponent {
const {
intl,
userIsPresenter,
isFullscreen,
} = this.props;
const { isFullscreen } = this.state;
if (userIsPresenter || !ALLOW_FULLSCREEN) return null;

View File

@ -12,7 +12,6 @@ const PresentationAreaContainer = ({ presentationPodIds, mountPresentationArea,
export default withTracker(({ podId }) => {
const currentSlide = PresentationAreaService.getCurrentSlide(podId);
const presentationIsDownloadable = PresentationAreaService.isPresentationDownloadable(podId);
const isFullscreen = Session.get('isFullscreen');
return {
currentSlide,
downloadPresentationUri: PresentationAreaService.downloadPresentationUri(podId),
@ -20,7 +19,6 @@ export default withTracker(({ podId }) => {
multiUser: PresentationAreaService.getMultiUserStatus(currentSlide && currentSlide.id)
&& !getSwapLayout(),
presentationIsDownloadable,
isFullscreen,
mountPresentationArea: !!currentSlide,
currentPresentation: PresentationAreaService.getCurrentPresentation(podId),
notify,

View File

@ -2,6 +2,7 @@ import React from 'react';
import { defineMessages, injectIntl, intlShape } from 'react-intl';
import PropTypes from 'prop-types';
import _ from 'lodash';
import FullscreenService from '../fullscreen-button/service';
import FullscreenButtonContainer from '../fullscreen-button/container';
import { styles } from './styles';
@ -19,14 +20,19 @@ class ScreenshareComponent extends React.Component {
super();
this.state = {
loaded: false,
isFullscreen: false,
};
this.onVideoLoad = this.onVideoLoad.bind(this);
this.onFullscreenChange = this.onFullscreenChange.bind(this);
}
componentDidMount() {
const { presenterScreenshareHasStarted } = this.props;
presenterScreenshareHasStarted();
document.onfullscreenchange = () => this.onFullscreenChange();
document.addEventListener('fullscreenchange', () => this.onFullscreenChange());
}
componentWillReceiveProps(nextProps) {
@ -50,8 +56,17 @@ class ScreenshareComponent extends React.Component {
this.setState({ loaded: true });
}
onFullscreenChange() {
const { isFullscreen } = this.state;
const newIsFullscreen = FullscreenService.isFullScreen(this.screenshareContainer);
if (isFullscreen !== newIsFullscreen) {
this.setState({ isFullscreen: newIsFullscreen });
}
}
renderFullscreenButton() {
const { intl, isFullscreen } = this.props;
const { intl } = this.props;
const { isFullscreen } = this.state;
if (!ALLOW_FULLSCREEN) return null;
@ -109,9 +124,4 @@ ScreenshareComponent.propTypes = {
unshareScreen: PropTypes.func.isRequired,
presenterScreenshareHasEnded: PropTypes.func.isRequired,
presenterScreenshareHasStarted: PropTypes.func.isRequired,
isFullscreen: PropTypes.bool,
};
ScreenshareComponent.defaultProps = {
isFullscreen: false,
};

View File

@ -20,13 +20,11 @@ const ScreenshareContainer = (props) => {
export default withTracker(() => {
const user = Users.findOne({ userId: Auth.userID });
const MappedUser = mapUser(user);
const isFullscreen = Session.get('isFullscreen');
return {
isPresenter: MappedUser.isPresenter,
unshareScreen,
isVideoBroadcasting,
presenterScreenshareHasStarted,
presenterScreenshareHasEnded,
isFullscreen,
};
})(ScreenshareContainer);

View File

@ -21,12 +21,13 @@ const propTypes = {
joinVideo: PropTypes.func,
resolve: PropTypes.func,
hasMediaDevices: PropTypes.bool.isRequired,
webcamDeviceId: PropTypes.string.isRequired,
webcamDeviceId: PropTypes.string,
};
const defaultProps = {
joinVideo: null,
resolve: null,
webcamDeviceId: null,
};
const intlMessages = defineMessages({

View File

@ -115,11 +115,6 @@ const propTypes = {
userId: PropTypes.string.isRequired,
intl: PropTypes.objectOf(Object).isRequired,
enableVideoStats: PropTypes.bool.isRequired,
isFullscreen: PropTypes.bool,
};
const defaultProps = {
isFullscreen: false,
};
class VideoProvider extends Component {
@ -1158,7 +1153,6 @@ class VideoProvider extends Component {
const {
users,
enableVideoStats,
isFullscreen,
} = this.props;
return (
<VideoList
@ -1167,13 +1161,11 @@ class VideoProvider extends Component {
getStats={this.getStats}
stopGettingStats={this.stopGettingStats}
enableVideoStats={enableVideoStats}
isFullscreen={isFullscreen}
/>
);
}
}
VideoProvider.propTypes = propTypes;
VideoProvider.defaultProps = defaultProps;
export default injectIntl(VideoProvider);

View File

@ -10,7 +10,6 @@ const VideoProviderContainer = ({ children, ...props }) => {
};
export default withTracker((props) => {
const isFullscreen = Session.get('isFullscreen');
return {
cursor: props.cursor,
swapLayout: props.swapLayout,
@ -21,6 +20,5 @@ export default withTracker((props) => {
userName: VideoService.userName(),
enableVideoStats: getFromUserSettings('enableVideoStats', Meteor.settings.public.kurento.enableVideoStats),
voiceBridge: VideoService.voiceBridge(),
isFullscreen,
};
})(VideoProviderContainer);

View File

@ -15,11 +15,6 @@ const propTypes = {
enableVideoStats: PropTypes.bool.isRequired,
webcamDraggableDispatch: PropTypes.func.isRequired,
intl: PropTypes.objectOf(Object).isRequired,
isFullscreen: PropTypes.bool,
};
const defaultProps = {
isFullscreen: false,
};
const intlMessages = defineMessages({
@ -159,7 +154,6 @@ class VideoList extends Component {
getStats,
stopGettingStats,
enableVideoStats,
isFullscreen,
} = this.props;
const { focusedId } = this.state;
@ -195,7 +189,6 @@ class VideoList extends Component {
getStats={(videoRef, callback) => getStats(user.id, videoRef, callback)}
stopGettingStats={() => stopGettingStats(user.id)}
enableVideoStats={enableVideoStats}
isFullscreen={isFullscreen}
/>
</div>
);
@ -243,6 +236,5 @@ class VideoList extends Component {
}
VideoList.propTypes = propTypes;
VideoList.defaultProps = defaultProps;
export default injectIntl(withDraggableConsumer(VideoList));

View File

@ -14,6 +14,7 @@ import DropdownListItem from '/imports/ui/components/dropdown/list/item/componen
import Icon from '/imports/ui/components/icon/component';
import logger from '/imports/startup/client/logger';
import VideoListItemStats from './video-list-item-stats/component';
import FullscreenService from '../../../fullscreen-button/service';
import FullscreenButtonContainer from '../../../fullscreen-button/container';
import { styles } from '../styles';
import { withDraggableConsumer } from '../../../media/webcam-draggable-overlay/context';
@ -40,6 +41,7 @@ class VideoListItem extends Component {
this.toggleStats = this.toggleStats.bind(this);
this.setStats = this.setStats.bind(this);
this.setVideoIsReady = this.setVideoIsReady.bind(this);
this.onFullscreenChange = this.onFullscreenChange.bind(this);
}
componentDidMount() {
@ -55,6 +57,7 @@ class VideoListItem extends Component {
onMount(this.videoTag);
this.videoTag.addEventListener('loadeddata', () => this.setVideoIsReady());
document.addEventListener('fullscreenchange', () => this.onFullscreenChange());
}
componentDidUpdate() {
@ -81,6 +84,19 @@ class VideoListItem extends Component {
}
}
onFullscreenChange() {
const { webcamDraggableState, webcamDraggableDispatch } = this.props;
const isFullscreen = FullscreenService.isFullScreen(this.videoContainer);
if (webcamDraggableState.isFullscreen !== isFullscreen) {
webcamDraggableDispatch(
{
type: 'setIsFullscreen',
value: isFullscreen,
},
);
}
}
setStats(updatedStats) {
const { stats } = this.state;
const { audio, video } = updatedStats;
@ -129,7 +145,7 @@ class VideoListItem extends Component {
}
renderFullscreenButton() {
const { user, isFullscreen } = this.props;
const { user, webcamDraggableState } = this.props;
if (!ALLOW_FULLSCREEN) return null;
@ -137,7 +153,7 @@ class VideoListItem extends Component {
<FullscreenButtonContainer
fullscreenRef={this.videoContainer}
elementName={user.name}
isFullscreen={isFullscreen}
isFullscreen={webcamDraggableState.isFullscreen}
dark
/>
);
@ -149,7 +165,6 @@ class VideoListItem extends Component {
user,
numOfUsers,
webcamDraggableState,
isFullscreen,
} = this.props;
const availableActions = this.getAvailableActions();
const enableVideoMenu = Meteor.settings.public.kurento.enableVideoMenu || false;
@ -175,8 +190,10 @@ class VideoListItem extends Component {
muted
className={cx({
[styles.media]: true,
[styles.cursorGrab]: !webcamDraggableState.dragging && !isFullscreen,
[styles.cursorGrabbing]: webcamDraggableState.dragging && !isFullscreen,
[styles.cursorGrab]: !webcamDraggableState.dragging
&& !webcamDraggableState.isFullscreen,
[styles.cursorGrabbing]: webcamDraggableState.dragging
&& !webcamDraggableState.isFullscreen,
})}
ref={(ref) => { this.videoTag = ref; }}
autoPlay