Fix webcams/sfu reconnect on every render and styles for 4:3
This commit is contained in:
parent
ed8d570a3d
commit
5b9433fc9f
@ -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>
|
||||
);
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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)}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
|
@ -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>
|
||||
);
|
||||
|
@ -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;
|
||||
|
||||
|
@ -20,10 +20,6 @@ class VideoListItem extends Component {
|
||||
this.props.onMount(this.videoTag);
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
this.props.onUnmount();
|
||||
}
|
||||
|
||||
render() {
|
||||
const { user, actions } = this.props;
|
||||
|
||||
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user