Fix webcams/sfu reconnect on every render and styles for 4:3

This commit is contained in:
Oswaldo Acauan 2018-04-11 15:50:14 -03:00
parent ed8d570a3d
commit 5b9433fc9f
10 changed files with 116 additions and 120 deletions

View File

@ -2,60 +2,48 @@ import React, { Component } from 'react';
import PropTypes from 'prop-types';
import cx from 'classnames';
import { styles } from './styles';
import VideoProviderContainer from '../video-provider/container';
const propTypes = {
content: PropTypes.element.isRequired,
overlay: PropTypes.element,
children: PropTypes.element.isRequired,
floatingOverlay: PropTypes.bool,
hideOverlay: PropTypes.bool,
};
const defaultProps = {
overlay: null,
floatingOverlay: false,
hideOverlay: true,
};
export default class Media extends Component {
renderContent() {
const { content, hideOverlay } = this.props;
return (
<div
className={cx({
[styles.content]: true,
[styles.hasOverlay]: !hideOverlay,
})}
>
{content}
</div>
);
}
renderOverlay() {
const { overlay, floatingOverlay, hideOverlay } = this.props;
if (!overlay) return null;
return (
<div
className={cx({
[styles.overlay]: true,
[styles.hideOverlay]: hideOverlay,
[styles.floatingOverlay]: floatingOverlay,
})}
>
{overlay}
</div>
);
componentWillUpdate() {
window.dispatchEvent(new Event('resize'));
}
render() {
const {
swapLayout, floatingOverlay, hideOverlay, disableVideo,
} = this.props;
const contentClassName = cx({
[styles.content]: true,
[styles.hasOverlay]: !hideOverlay,
});
const overlayClassName = cx({
[styles.overlay]: true,
[styles.hideOverlay]: hideOverlay,
[styles.floatingOverlay]: floatingOverlay,
});
return (
<div className={styles.container}>
{this.props.children}
{this.renderContent()}
{this.renderOverlay()}
<div className={!swapLayout ? contentClassName : overlayClassName}>
{this.props.children}
</div>
<div className={!swapLayout ? overlayClassName : contentClassName}>
{ !disableVideo ? <VideoProviderContainer /> : null }
</div>
</div>
);
}

View File

@ -7,16 +7,9 @@ import VideoService from '/imports/ui/components/video-provider/service';
import Media from './component';
import MediaService, { getSwapLayout } from './service';
import PresentationAreaContainer from '../presentation/container';
import VideoProviderContainer from '../video-provider/container';
import ScreenshareContainer from '../screenshare/container';
import DefaultContent from '../presentation/default-content/component';
const defaultProps = {
overlay: null,
content: <PresentationAreaContainer />,
defaultContent: <DefaultContent />,
};
const intlMessages = defineMessages({
screenshareStarted: {
id: 'app.media.screenshare.start',
@ -45,12 +38,10 @@ class MediaContainer extends Component {
}
render() {
return <Media {...this.props}>{this.props.children}</Media>;
return <Media {...this.props} />;
}
}
MediaContainer.defaultProps = defaultProps;
export default withTracker(() => {
const { dataSaving } = Settings;
const { viewParticipantsWebcams, viewScreenshare } = dataSaving;
@ -58,34 +49,28 @@ export default withTracker(() => {
const data = {};
data.currentPresentation = MediaService.getPresentationInfo();
data.content = <DefaultContent />;
data.children = <DefaultContent />;
if (MediaService.shouldShowWhiteboard()) {
data.content = <PresentationAreaContainer />;
data.children = <PresentationAreaContainer />;
}
if (MediaService.shouldShowScreenshare() && (viewScreenshare || MediaService.isUserPresenter())) {
data.content = <ScreenshareContainer />;
data.children = <ScreenshareContainer />;
}
const usersVideo = VideoService.getAllUsersVideo();
if (MediaService.shouldShowOverlay() && viewParticipantsWebcams) {
if (MediaService.shouldShowOverlay() && usersVideo.length) {
data.floatingOverlay = usersVideo.length < 2;
data.hideOverlay = usersVideo.length === 0;
data.overlay = <VideoProviderContainer />;
}
data.isScreensharing = MediaService.isVideoBroadcasting();
data.swapLayout = getSwapLayout() && usersVideo.length > 0 && viewParticipantsWebcams;
data.disableVideo = !viewParticipantsWebcams;
const shouldSwapLayout = getSwapLayout();
if (shouldSwapLayout) {
return {
...data,
hideOverlay: false,
floatingOverlay: true,
overlay: data.content,
content: data.overlay,
};
if (data.swapLayout) {
data.floatingOverlay = true;
}
return data;

View File

@ -24,6 +24,7 @@
display: flex;
order: 1;
width: 100%;
margin-bottom: 10px;
}
.hideOverlay {
@ -37,6 +38,7 @@ $overlay-ratio: 16 / 9;
.floatingOverlay {
@include mq($medium-up) {
z-index: 1;
position: fixed;
bottom: .8rem;
right: .8rem;

View File

@ -8,7 +8,7 @@ import Toast from '/imports/ui/components/toast/component';
import _ from 'lodash';
import VideoService from './service';
import VideoDockContainer from './video-dock/container';
import VideoList from './video-list/component';
const intlMessages = defineMessages({
iceCandidateError: {
@ -76,6 +76,25 @@ class VideoProvider extends Component {
this.ws.addEventListener('message', this.onWsMessage);
}
shouldComponentUpdate({ users: nextUsers }, nextState) {
const { users } = this.props;
return !_.isEqual(this.state, nextState) || users.length !== nextUsers.length;
}
componentWillUpdate({ users, userId }) {
const usersSharingIds = users.map(u => u.id);
const usersConnected = Object.keys(this.webRtcPeers);
const usersToConnect = usersSharingIds.filter(id => !usersConnected.includes(id));
const usersToDisconnect = usersConnected.filter(id => !usersSharingIds.includes(id));
usersToConnect.forEach(id => this.createWebRTCPeer(id, userId === id));
usersToDisconnect.forEach(id => this.destroyWebRTCPeer(id, userId === id));
console.warn('[usersToConnect]', usersToConnect);
console.warn('[usersToDisconnect]', usersToDisconnect);
}
componentWillUnmount() {
document.removeEventListener('joinVideo', this.shareWebcam);
document.removeEventListener('exitVideo', this.unshareWebcam);
@ -90,7 +109,6 @@ class VideoProvider extends Component {
// Unshare user webcam
if (this.state.sharedWebcam) {
this.unshareWebcam();
this.stop(this.props.userId);
}
Object.keys(this.webRtcPeers).forEach((id) => {
@ -224,9 +242,21 @@ class VideoProvider extends Component {
}
}
destroyWebRTCPeer(id) {
destroyWebRTCPeer(id, shareWebcam = false) {
const webRtcPeer = this.webRtcPeers[id];
if (shareWebcam) {
this.sendMessage({
type: 'video',
role: 'share',
id: 'stop',
cameraId: id,
});
this.unshareWebcam();
VideoService.exitedVideo();
}
// Clear the shared camera fail timeout when destroying
clearTimeout(this.cameraTimeouts[id]);
this.cameraTimeouts[id] = null;
@ -245,7 +275,7 @@ class VideoProvider extends Component {
}
}
initWebRTC(id, shareWebcam, tag) {
createWebRTCPeer(id, shareWebcam) {
const that = this;
const { intl, meetingId } = this.props;
@ -272,17 +302,14 @@ class VideoProvider extends Component {
onicecandidate: this.getOnIceCandidateCallback(id, shareWebcam),
};
let peerObj;
let WebRtcPeerObj = window.kurentoUtils.WebRtcPeer.WebRtcPeerRecvonly;
if (shareWebcam) {
WebRtcPeerObj = window.kurentoUtils.WebRtcPeer.WebRtcPeerSendonly;
this.shareWebcam();
peerObj = kurentoUtils.WebRtcPeer.WebRtcPeerSendonly;
options.localVideo = tag;
} else {
peerObj = kurentoUtils.WebRtcPeer.WebRtcPeerRecvonly;
options.remoteVideo = tag;
}
const webRtcPeer = new peerObj(options, function (error) {
that.webRtcPeers[id] = new WebRtcPeerObj(options, function (error) {
if (error) {
log('error', ' WebRTC peerObj create error');
log('error', error);
@ -302,14 +329,13 @@ class VideoProvider extends Component {
return log('error', error);
}
if (typeof that.webRtcPeers[id].onReady === 'function') {
that.webRtcPeers[id].onReady;
}
this.didSDPAnswered = false;
this.iceQueue = [];
that.webRtcPeers[id] = webRtcPeer;
if (shareWebcam) {
that.sharedWebcam = webRtcPeer;
}
this.generateOffer((error, offerSdp) => {
if (error) {
log('error', ' WebRtc generate offer error');
@ -325,8 +351,8 @@ class VideoProvider extends Component {
that.unshareWebcam();
VideoService.exitedVideo();
} else {
that.stop(id);
that.initWebRTC(id, shareWebcam, videoOptions, tag);
that.destroyWebRTCPeer(id);
that.createWebRTCPeer(id, shareWebcam);
}
}, CAMERA_SHARE_FAILED_WAIT_TIME);
@ -369,22 +395,26 @@ class VideoProvider extends Component {
};
}
stop(id) {
const userId = this.props.userId;
attachVideoStream(id, video) {
if (video.srcObject) return; // Skip if the stream is already attached
if (id === userId) {
this.sendMessage({
type: 'video',
role: id == userId ? 'share' : 'viewer',
id: 'stop',
cameraId: id,
});
const isCurrent = id === this.props.userId;
const peer = this.webRtcPeers[id];
const { peerConnection } = peer;
this.unshareWebcam();
VideoService.exitedVideo();
const attachVideoStream = () => {
const stream = isCurrent ? peer.getLocalStream() : peer.getRemoteStream();
video.pause();
video.srcObject = stream;
video.load();
};
if (peerConnection.iceGatheringState === 'completed') {
attachVideoStream();
return;
}
this.destroyWebRTCPeer(id);
peer.on('candidategatheringdone', attachVideoStream);
}
handlePlayStop(message) {
@ -392,7 +422,7 @@ class VideoProvider extends Component {
log('info', 'Handle play stop <--------------------');
log('error', message);
this.stop(id);
this.destroyWebRTCPeer(id);
}
handlePlayStart(message) {
@ -416,7 +446,7 @@ class VideoProvider extends Component {
VideoService.exitedVideo();
this.notifyError(intl.formatMessage(intlMessages.sharingError));
} else {
this.stop(message.cameraId);
this.destroyWebRTCPeer(message.cameraId);
}
console.error(' Handle error --------------------->');
@ -442,18 +472,18 @@ class VideoProvider extends Component {
unshareWebcam() {
log('info', 'Unsharing webcam');
this.setState({ ...this.state, sharedWebcam: false });
this.setState({ sharedWebcam: false });
VideoService.sendUserUnshareWebcam(this.props.userId);
}
render() {
if (!this.state.socketOpen) return null;
return (
<VideoDockContainer
onStart={this.initWebRTC.bind(this)}
onStop={this.stop.bind(this)}
socketOpen={this.state.socketOpen}
isLocked={this.props.isLocked}
<VideoList
users={this.props.users}
onMount={this.attachVideoStream.bind(this)}
/>
);
}

View File

@ -3,10 +3,11 @@ import { withTracker } from 'meteor/react-meteor-data';
import VideoProvider from './component';
import VideoService from './service';
const VideoProviderContainer = ({ children, ...props }) => <VideoProvider {...props}>{children}</VideoProvider>;
const VideoProviderContainer = ({ children, ...props }) =>
(!props.users.length ? null : <VideoProvider {...props}>{children}</VideoProvider>);
export default withTracker(() => ({
isLocked: VideoService.isLocked(),
meetingId: VideoService.meetingId(),
users: VideoService.getAllUsersVideo(),
userId: VideoService.userId(),
}))(VideoProviderContainer);

View File

@ -9,6 +9,7 @@ import UserListService from '/imports/ui/components/user-list/service';
class VideoService {
constructor() {
this.defineProperties({
isSharing: false,
isConnected: false,
isWaitingResponse: false,
});
@ -36,6 +37,7 @@ class VideoService {
}
joinVideo() {
this.isSharing = true;
const joinVideoEvent = new Event('joinVideo');
document.dispatchEvent(joinVideoEvent);
}
@ -50,11 +52,13 @@ class VideoService {
}
exitVideo() {
this.isSharing = false;
const exitVideoEvent = new Event('exitVideo');
document.dispatchEvent(exitVideoEvent);
}
exitedVideo() {
console.warn('exitedVideo');
this.isWaitingResponse = false;
this.isConnected = false;
}
@ -64,7 +68,6 @@ class VideoService {
}
sendUserUnshareWebcam(stream) {
this.isWaitingResponse = true;
makeCall('userUnshareWebcam', stream);
}
@ -78,7 +81,7 @@ class VideoService {
const isLocked = this.isLocked();
const currentUser = Users.findOne({ userId });
const currentUserIsModerator = mapUser(currentUser).isModerator;
const sharedWebcam = this.isWaitingResponse || this.isConnected;
const sharedWebcam = this.isSharing;
const isSharingWebcam = user => user.isSharingWebcam || (sharedWebcam && user.isCurrent);
const isNotLocked = user => !(isLocked && user.isLocked);

View File

@ -9,7 +9,6 @@ import VideoListItem from './video-list-item/component';
const propTypes = {
users: PropTypes.arrayOf(PropTypes.object).isRequired,
onMount: PropTypes.func.isRequired,
onUnmount: PropTypes.func.isRequired,
};
const intlMessages = defineMessages({
@ -85,7 +84,7 @@ class VideoList extends Component {
setOptimalGrid() {
let numItems = this.props.users.length;
if (numItems < 1) {
if (numItems < 1 || !this.canvas || !this.grid) {
return;
}
@ -137,7 +136,7 @@ class VideoList extends Component {
renderVideoList() {
const {
intl, users, onMount, onUnmount,
intl, users, onMount,
} = this.props;
const { focusedId } = this.state;
@ -164,9 +163,8 @@ class VideoList extends Component {
actions={actions}
onMount={(videoRef) => {
this.handleCanvasResize();
return onMount(user.id, user.isCurrent, videoRef);
return onMount(user.id, videoRef);
}}
onUnmount={() => onUnmount(user.id)}
/>
</div>
);

View File

@ -13,14 +13,11 @@
.videoList {
display: grid;
grid-gap: 10px;
margin-bottom: 10px;
overflow: hidden;
grid-auto-flow: dense;
}
.videoListItem {
width: 100%;
height: 100%;
overflow: hidden;
display: flex;

View File

@ -20,10 +20,6 @@ class VideoListItem extends Component {
this.props.onMount(this.videoTag);
}
componentWillUnmount() {
this.props.onUnmount();
}
render() {
const { user, actions } = this.props;

View File

@ -17,16 +17,12 @@ const isDisabled = () => {
const isWaitingResponse = VideoService.isWaitingResponse();
const isConnected = VideoService.isConnected();
const videoSettings = Settings.dataSaving;
const enableShare = videoSettings.viewParticipantsWebcams;
const enableShare = Settings.dataSaving.viewParticipantsWebcams;
const lockCam = VideoService.isLocked();
const user = Users.findOne({ userId: Auth.userID });
const userLocked = mapUser(user).isLocked;
const isConnecting = (!isSharingVideo && isConnected);
const isLocked = (lockCam && userLocked);
return isLocked