diff --git a/bigbluebutton-html5/imports/api/video/server/handlers/userSharedHtml5Webcam.js b/bigbluebutton-html5/imports/api/video/server/handlers/userSharedHtml5Webcam.js index e0101f99917c3dd917c380cf83309c1c35d0c397..abf5466ffea602df604717d33c941c39c0b3316b 100644 --- a/bigbluebutton-html5/imports/api/video/server/handlers/userSharedHtml5Webcam.js +++ b/bigbluebutton-html5/imports/api/video/server/handlers/userSharedHtml5Webcam.js @@ -1,11 +1,11 @@ import sharedWebcam from '../modifiers/sharedWebcam'; import {check} from 'meteor/check'; -export default function handleUserSharedHtml5Webcam({ header, payload }) { - const meetingId = header.meetingId; - const userId = header.userId; - +export default function handleUserSharedHtml5Webcam({ header }, meetingId ) { + check(header, Object); + const { userId } = header; check(meetingId, String); + check(userId, String); return sharedWebcam(meetingId, userId); } diff --git a/bigbluebutton-html5/imports/api/video/server/handlers/userUnsharedHtml5Webcam.js b/bigbluebutton-html5/imports/api/video/server/handlers/userUnsharedHtml5Webcam.js index d982c8daabc718f5f5f59c3efae563a7ef9885c6..bf6ba596e487375e198c671f8fe154f4e4da7c18 100644 --- a/bigbluebutton-html5/imports/api/video/server/handlers/userUnsharedHtml5Webcam.js +++ b/bigbluebutton-html5/imports/api/video/server/handlers/userUnsharedHtml5Webcam.js @@ -1,11 +1,11 @@ import unsharedWebcam from '../modifiers/unsharedWebcam'; import { check } from 'meteor/check'; -export default function handleUserUnsharedHtml5Webcam({ header, payload }) { - const meetingId = header.meetingId; - const userId = header.userId; - +export default function handleUserUnsharedHtml5Webcam({ header }, meetingId) { + check(header, Object); + const { userId } = header; check(meetingId, String); + check(userId, String); return unsharedWebcam(meetingId, userId); } diff --git a/bigbluebutton-html5/imports/ui/components/actions-bar/component.jsx b/bigbluebutton-html5/imports/ui/components/actions-bar/component.jsx index 4412ec793fc341fee7836720065ad6f36760bf1b..0ab79ef349c49eaa2d7cd8522d7cfc3712d66eeb 100644 --- a/bigbluebutton-html5/imports/ui/components/actions-bar/component.jsx +++ b/bigbluebutton-html5/imports/ui/components/actions-bar/component.jsx @@ -7,8 +7,6 @@ import JoinVideoOptionsContainer from '../video-dock/video-menu/container'; const ActionsBar = ({ isUserPresenter, - handleExitAudio, - handleOpenJoinAudio, handleExitVideo, handleJoinVideo, handleShareScreen, diff --git a/bigbluebutton-html5/imports/ui/components/app/container.jsx b/bigbluebutton-html5/imports/ui/components/app/container.jsx index d8d0b54459de4d3f028644695ea34bd7184b6287..88abd7c9b6e1e4d076700b4a8e27247437c05537 100644 --- a/bigbluebutton-html5/imports/ui/components/app/container.jsx +++ b/bigbluebutton-html5/imports/ui/components/app/container.jsx @@ -7,7 +7,6 @@ import Auth from '/imports/ui/services/auth'; import Users from '/imports/api/users'; import Breakouts from '/imports/api/breakouts'; import Meetings from '/imports/api/meetings'; -import Screenshare from '/imports/api/screenshare'; import ClosedCaptionsContainer from '/imports/ui/components/closed-captions/container'; diff --git a/bigbluebutton-html5/imports/ui/components/video-dock/component.jsx b/bigbluebutton-html5/imports/ui/components/video-dock/component.jsx index 58d226c13143fbbe0039f8b76b82fdeee40837ad..4c4271dfe7fb55acf43356deba5dbf3cc9ee280a 100644 --- a/bigbluebutton-html5/imports/ui/components/video-dock/component.jsx +++ b/bigbluebutton-html5/imports/ui/components/video-dock/component.jsx @@ -4,10 +4,6 @@ import styles from './styles'; import { log } from '/imports/ui/services/api'; -window.addEventListener('resize', () => { - window.adjustVideos('webcamArea', true); -}); - class VideoElement extends Component { constructor(props) { super(props); @@ -18,32 +14,31 @@ export default class VideoDock extends Component { constructor(props) { super(props); + // Set a valid bbb-webrtc-sfu application server socket in the settings + this.ws = new ReconnectingWebSocket(Meteor.settings.public.kurento.wsUrl); + this.wsQueue = []; + this.webRtcPeers = {}; + this.reconnectWebcam = false; + this.reconnectList = false; + this.sharedCameraTimeout = null; + this.subscribedCamerasTimeouts = []; + this.state = { - // Set a valid kurento application server socket in the settings - ws: new ReconnectingWebSocket(Meteor.settings.public.kurento.wsUrl), - webRtcPeers: {}, - wsQueue: [], - reconnectWebcam: false, - reconnectList: [], - sharedCameraTimeout: null, - subscribedCamerasTimeouts: [], videos: {}, }; - window.ws = this.state.ws; - - this.state.ws.addEventListener('open', () => { + this.ws.addEventListener('open', () => { log('debug', '------ Websocket connection opened.'); // -- Resend queued messages that happened when socket was not connected - while (this.state.wsQueue.length > 0) { - this.sendMessage(this.state.wsQueue.pop()); + while (this.wsQueue.length > 0) { + this.sendMessage(this.wsQueue.pop()); } this.reconnectVideos(); }); - this.state.ws.addEventListener('close', (error) => { + this.ws.addEventListener('close', (error) => { log('debug', '------ Websocket connection closed.'); this.setupReconnectVideos(); @@ -57,18 +52,18 @@ export default class VideoDock extends Component { } setupReconnectVideos() { - for (id in this.state.webRtcPeers) { + for (id in this.webRtcPeers) { this.disconnected(id); this.stop(id); } } reconnectVideos() { - for (i in this.state.reconnectList) { - const id = this.state.reconnectList[i]; + for (i in this.reconnectList) { + const id = this.reconnectList[i]; // TODO: base this on BBB API users instead of using memory - if (id != this.state.myId) { + if (id != this.myId) { setTimeout(() => { log('debug', ` [camera] Trying to reconnect camera ${id}`); this.start(id, false, this.refs.videoInput); @@ -76,17 +71,17 @@ export default class VideoDock extends Component { } } - if (this.state.reconnectWebcam) { - log('debug', ` [camera] Trying to re-share ${this.state.myId} after reconnect.`); - this.start(this.state.myId, true, this.refs.videoInput); + if (this.reconnectWebcam) { + log('debug', ` [camera] Trying to re-share ${this.myId} after reconnect.`); + this.start(this.myId, true, this.refs.videoInput); } - this.setState({ reconnectWebcam: false, reconnectList: [] }); + this.reconnectWebcam = false; + this.reconnectList = []; } componentDidMount() { - const that = this; - const ws = this.state.ws; + const ws = this.ws; const { users } = this.props; for (let i = 0; i < users.length; i++) { if (users[i].has_stream) { @@ -94,66 +89,83 @@ export default class VideoDock extends Component { } } - document.addEventListener('joinVideo', () => { that.shareWebcam(); });// TODO find a better way to do this - document.addEventListener('exitVideo', () => { that.unshareWebcam(); }); + document.addEventListener('joinVideo', this.shareWebcam.bind(this));// TODO find a better way to do this + document.addEventListener('exitVideo', this.unshareWebcam.bind(this)); - ws.addEventListener('message', (msg) => { - const parsedMessage = JSON.parse(msg.data); + window.addEventListener('resize', this.adjustVideos); - console.log('Received message new ws message: '); - console.log(parsedMessage); + ws.addEventListener('message', this.onWsMessage.bind(this)); + } - switch (parsedMessage.id) { - case 'startResponse': - this.startResponse(parsedMessage); - break; + componentWillMount () { + this.ws.onopen = () => { + while (this.wsQueue.length > 0) { + this.sendMessage(this.wsQueue.pop()); + } + }; + } - case 'error': - this.handleError(parsedMessage); - break; + componentWillUnmount () { + document.removeEventListener('joinVideo', this.shareWebcam); + document.removeEventListener('exitVideo', this.shareWebcam); + window.removeEventListener('resize', this.adjustVideos); + this.ws.removeEventListener('message', this.onWsMessage); + } - case 'playStart': - this.handlePlayStart(parsedMessage); - break; + adjustVideos () { + window.adjustVideos('webcamArea', true); + } - case 'playStop': - this.handlePlayStop(parsedMessage); + onWsMessage (msg) { + const parsedMessage = JSON.parse(msg.data); - break; + console.log('Received message new ws message: '); + console.log(parsedMessage); - case 'iceCandidate': + switch (parsedMessage.id) { - const webRtcPeer = this.state.webRtcPeers[parsedMessage.cameraId]; + case 'startResponse': + this.startResponse(parsedMessage); + break; - if (webRtcPeer !== null) { - if (webRtcPeer.didSDPAnswered) { - webRtcPeer.addIceCandidate(parsedMessage.candidate, (err) => { - if (err) { - return log('error', `Error adding candidate: ${err}`); - } - }); - } else { + case 'error': + this.handleError(parsedMessage); + break; - // If we are ever in a position where iceQueue is not defined by this point - if (typeof webRtcPeer.iceQueue === 'undefined') { - webRtcPeer.iceQueue = []; - } + case 'playStart': + this.handlePlayStart(parsedMessage); + break; - webRtcPeer.iceQueue.push(parsedMessage.candidate); - } + case 'playStop': + this.handlePlayStop(parsedMessage); + + break; + + case 'iceCandidate': + + const webRtcPeer = this.webRtcPeers[parsedMessage.cameraId]; + + if (webRtcPeer !== null) { + if (webRtcPeer.didSDPAnswered) { + webRtcPeer.addIceCandidate(parsedMessage.candidate, (err) => { + if (err) { + return log('error', `Error adding candidate: ${err}`); + } + }); } else { - log('error', ' [ICE] Message arrived before webRtcPeer?'); + webRtcPeer.iceQueue.push(parsedMessage.candidate); } - - break; - } - }); - } + } else { + log('error', ' [ICE] Message arrived before webRtcPeer?'); + } + break; + } + }; start(id, shareWebcam, videoInput) { const that = this; - const ws = this.state.ws; + const ws = this.ws; console.log(`Starting video call for video: ${id}`); log('info', 'Creating WebRtcPeer and generating local sdp offer ...'); @@ -202,7 +214,7 @@ export default class VideoDock extends Component { document.getElementById('webcamArea').appendChild(options.remoteVideo); } - this.state.webRtcPeers[id] = new peerObj(options, function (error) { + var webRtcPeer = new peerObj(options, function (error) { if (error) { log('error', ' WebRTC peerObj create error'); @@ -215,9 +227,10 @@ export default class VideoDock extends Component { this.didSDPAnswered = false; this.iceQueue = []; + that.webRtcPeers[id] = webRtcPeer; if (shareWebcam) { - that.state.sharedWebcam = that.state.webRtcPeers[id]; - that.state.myId = id; + that.sharedWebcam = webRtcPeer; + that.myId = id; } this.generateOffer((error, offerSdp) => { @@ -254,17 +267,13 @@ export default class VideoDock extends Component { } disconnected(id) { - if (this.state.sharedWebcam) { + if (this.sharedWebcam) { log('debug', ' [camera] Webcam disconnected, will try re-share webcam later.'); - this.setState({ reconnectWebcam: true }); + this.reconnectWebcam = true; } else { - const reconnectList = this.state.reconnectList; - - reconnectList.push(id); + this.reconnectList.push(id); log('debug', ` [camera] ${id} disconnected, will try re-subscribe later.`); - - this.setState({ reconnectList }); } } @@ -309,20 +318,25 @@ export default class VideoDock extends Component { } destroyWebRTCPeer(id) { - const webRtcPeer = this.state.webRtcPeers[id]; + const webRtcPeer = this.webRtcPeers[id]; if (webRtcPeer) { log('info', 'Stopping WebRTC peer'); + if (id == this.myId && this.sharedWebcam) { + this.sharedWebcam.dispose(); + this.sharedWebcam = null; + } + webRtcPeer.dispose(); - delete this.state.webRtcPeers[id]; + delete this.webRtcPeers[id]; } else { log('info', 'No WebRTC peer to stop (not an error)'); } - if (this.state.sharedWebcam) { - this.state.sharedWebcam.dispose(); - this.state.sharedWebcam = null; + if (this.sharedWebcam) { + this.sharedWebcam.dispose(); + this.sharedWebcam = null; } else { log('info', 'No shared camera WebRTC peer to stop (not an error)'); } @@ -349,7 +363,7 @@ export default class VideoDock extends Component { startResponse(message) { const id = message.cameraId; - const webRtcPeer = this.state.webRtcPeers[id]; + const webRtcPeer = this.webRtcPeers[id]; if (message.sdpAnswer == null) { return log('debug', 'Null sdp answer. Camera unplugged?'); @@ -371,7 +385,7 @@ export default class VideoDock extends Component { } sendMessage(message) { - const ws = this.state.ws; + const ws = this.ws; if (this.connectedToMediaServer()) { const jsonMessage = JSON.stringify(message); @@ -384,17 +398,17 @@ export default class VideoDock extends Component { } else { // No need to queue video stop messages if (message.id != 'stop') { - this.state.wsQueue.push(message); + this.wsQueue.push(message); } } } connectedToMediaServer() { - return ws.readyState == WebSocket.OPEN; + return this.ws.readyState === WebSocket.OPEN; } connectionStatus() { - return ws.readyState; + return this.ws.readyState; } handlePlayStop(message) { @@ -427,9 +441,9 @@ export default class VideoDock extends Component { componentWillUnmount() { // Close websocket connection to prevent multiple reconnects from happening - this.state.ws.close(); + this.ws.close(); - this.state.ws.removeEventListener('message', () => {}); + this.ws.removeEventListener('message', () => {}); } shouldComponentUpdate(nextProps, nextState) { diff --git a/bigbluebutton-html5/imports/ui/components/video-dock/container.jsx b/bigbluebutton-html5/imports/ui/components/video-dock/container.jsx index 0bd4ea2ee4d5f16a57ee285175169b4c161c94aa..a92bbb3d0989192cca4b2840832df304611843d5 100644 --- a/bigbluebutton-html5/imports/ui/components/video-dock/container.jsx +++ b/bigbluebutton-html5/imports/ui/components/video-dock/container.jsx @@ -3,7 +3,6 @@ import { createContainer } from 'meteor/react-meteor-data'; import VideoDock from './component'; import VideoService from './service'; -import Users from '/imports/api/users'; class VideoDockContainer extends Component { constructor(props) { @@ -22,5 +21,5 @@ class VideoDockContainer extends Component { export default createContainer(() => ({ sendUserShareWebcam: VideoService.sendUserShareWebcam, sendUserUnshareWebcam: VideoService.sendUserUnshareWebcam, - users: Users.find().fetch(), + users: VideoService.getAllUsers(), }), VideoDockContainer); diff --git a/bigbluebutton-html5/imports/ui/components/video-dock/service.js b/bigbluebutton-html5/imports/ui/components/video-dock/service.js index 99a791ecbfa05e767ec0f3155424a387c0c62b1f..5d778fca8f2385b81697876dc7773327f94bcbaa 100644 --- a/bigbluebutton-html5/imports/ui/components/video-dock/service.js +++ b/bigbluebutton-html5/imports/ui/components/video-dock/service.js @@ -1,4 +1,6 @@ import { makeCall } from '/imports/ui/services/api'; +import Users from '/imports/api/users'; +import { createContainer } from 'meteor/react-meteor-data'; const joinVideo = () => { var joinVideoEvent = new Event('joinVideo'); @@ -18,6 +20,10 @@ const sendUserUnshareWebcam = (stream) => { makeCall('userUnshareWebcam', stream); }; +const getAllUsers = () => { + return Users.find().fetch(); +} + export default { - sendUserShareWebcam, sendUserUnshareWebcam, joinVideo, exitVideo, + sendUserShareWebcam, sendUserUnshareWebcam, joinVideo, exitVideo, getAllUsers, }; diff --git a/bigbluebutton-html5/imports/ui/components/video-dock/styles.scss b/bigbluebutton-html5/imports/ui/components/video-dock/styles.scss index 4157b2c58194558e64ead582c2b654f8464acf8f..0292985c863c1e86a69754f5177b88298b8061f1 100644 --- a/bigbluebutton-html5/imports/ui/components/video-dock/styles.scss +++ b/bigbluebutton-html5/imports/ui/components/video-dock/styles.scss @@ -13,9 +13,6 @@ border-radius: .2rem; } -.secretButtons { -} - .sharedWebcamVideo { display: none; } diff --git a/bigbluebutton-html5/imports/ui/components/video-dock/video-menu/component.jsx b/bigbluebutton-html5/imports/ui/components/video-dock/video-menu/component.jsx index 9a2ca1c0181283ead37a71a3f41a49a083d8b02a..99d04b28e0dd125938c716f5c514e53ab494e15d 100755 --- a/bigbluebutton-html5/imports/ui/components/video-dock/video-menu/component.jsx +++ b/bigbluebutton-html5/imports/ui/components/video-dock/video-menu/component.jsx @@ -1,7 +1,6 @@ import React from 'react'; import { createContainer } from 'meteor/react-meteor-data'; import Button from '/imports/ui/components/button/component'; -import { withRouter } from 'react-router'; import { defineMessages, injectIntl } from 'react-intl'; const intlMessages = defineMessages({ @@ -50,4 +49,4 @@ class JoinVideoOptions extends React.Component { } } -export default withRouter(injectIntl(JoinVideoOptions)); +export default injectIntl(JoinVideoOptions); diff --git a/bigbluebutton-html5/imports/ui/components/video-dock/video-menu/container.jsx b/bigbluebutton-html5/imports/ui/components/video-dock/video-menu/container.jsx index da5950f90a3da413c568f53f93964149487501b2..da7d5517f5147408c91fba55207818fc666b7423 100755 --- a/bigbluebutton-html5/imports/ui/components/video-dock/video-menu/container.jsx +++ b/bigbluebutton-html5/imports/ui/components/video-dock/video-menu/container.jsx @@ -1,17 +1,12 @@ import React from 'react'; import { createContainer } from 'meteor/react-meteor-data'; -import Users from '/imports/api/users'; -import Auth from '/imports/ui/services/auth/index'; import JoinVideoOptions from './component'; +import VideoMenuService from './service'; const JoinVideoOptionsContainer = props => (<JoinVideoOptions {...props} />); export default createContainer((params) => { - const userId = Auth.userID; - const user = Users.findOne({ userId: userId }); - - const isSharingVideo = user.has_stream ? true : false; - + const isSharingVideo = VideoMenuService.isSharingVideo(); return { isSharingVideo, handleJoinVideo: params.handleJoinVideo, diff --git a/bigbluebutton-html5/imports/ui/components/video-dock/video-menu/service.js b/bigbluebutton-html5/imports/ui/components/video-dock/video-menu/service.js new file mode 100644 index 0000000000000000000000000000000000000000..92a58cd21afc50dac0196a3d19d325fea668e125 --- /dev/null +++ b/bigbluebutton-html5/imports/ui/components/video-dock/video-menu/service.js @@ -0,0 +1,12 @@ +import Users from '/imports/api/users'; +import Auth from '/imports/ui/services/auth/index'; + +const isSharingVideo = () => { + const userId = Auth.userID; + const user = Users.findOne({ userId: userId }); + return user.has_stream ? true : false; +}; + +export default { + isSharingVideo +}; diff --git a/bigbluebutton-html5/private/locales/en.json b/bigbluebutton-html5/private/locales/en.json index be13c502d43198332602b064798cef3bd12b2eaf..fef395731f67acece496fa4aa8d0362f17efa635 100644 --- a/bigbluebutton-html5/private/locales/en.json +++ b/bigbluebutton-html5/private/locales/en.json @@ -260,6 +260,6 @@ "app.guest.waiting": "Waiting for approval to join", "app.notification.recordingStart": "This session is now being recorded", "app.notification.recordingStop": "This session is not being recorded anymore", - "app.video.joinVideo": "Cam off", - "app.video.leaveVideo": "Cam on" + "app.video.joinVideo": "Share webcam", + "app.video.leaveVideo": "Unshare webcam" }