diff --git a/app/lib/RoomClient.js b/app/lib/RoomClient.js index c78769a..b146d48 100644 --- a/app/lib/RoomClient.js +++ b/app/lib/RoomClient.js @@ -53,7 +53,7 @@ export default class RoomClient 'constructor() [roomId:"%s", peerName:"%s", displayName:"%s", device:%s]', roomId, peerName, displayName, device.flag); - const signalingUrl = getSignalingUrl(peerName, roomId); + this._signalingUrl = getSignalingUrl(peerName, roomId); // window element to external login site this._loginWindow; @@ -76,18 +76,21 @@ export default class RoomClient // My peer name. this._peerName = peerName; + // My display name + this._displayName = peerName; + // Alert sound this._soundAlert = new Audio('/resources/sounds/notify.mp3'); + // AudioContext + this._audioContext; + // Socket.io peer connection - this._signalingSocket = io(signalingUrl); + this._signalingSocket = null; - if (this._device.flag === 'firefox') - ROOM_OPTIONS = Object.assign({ iceTransportPolicy: 'relay' }, ROOM_OPTIONS); - - // mediasoup-client Room instance. - this._room = new mediasoupClient.Room(ROOM_OPTIONS); - this._room.roomId = roomId; + // The mediasoup room instance + this._room = null; + this._roomId = roomId; // Our WebTorrent client this._webTorrent = this._torrentSupport && new WebTorrent({ @@ -102,7 +105,7 @@ export default class RoomClient this._maxSpotlights = ROOM_OPTIONS.maxSpotlights; // Manager of spotlight - this._spotlights = new Spotlights(this._maxSpotlights, this._room); + this._spotlights = null; // Transport for sending. this._sendTransport = null; @@ -140,7 +143,9 @@ export default class RoomClient this._startKeyListener(); - this._join({ displayName, device }); + this._audioContext = null; + + this.join(); } close() @@ -983,8 +988,36 @@ export default class RoomClient }, 500); } - _join({ displayName, device }) + async resumeAudio() { + logger.debug('resumeAudio()'); + try + { + await this._audioContext.resume(); + + store.dispatch( + stateActions.setAudioSuspended({ audioSuspended: false })); + + } + catch (error) + { + logger.error('resumeAudioJoin() failed: %o', error); + } + } + + join() + { + this._signalingSocket = io(this._signalingUrl); + + if (this._device.flag === 'firefox') + ROOM_OPTIONS = Object.assign({ iceTransportPolicy: 'relay' }, ROOM_OPTIONS); + + // mediasoup-client Room instance. + this._room = new mediasoupClient.Room(ROOM_OPTIONS); + this._room.roomId = this._roomId; + + this._spotlights = new Spotlights(this._maxSpotlights, this._room); + store.dispatch(stateActions.setRoomState('connecting')); this._signalingSocket.on('connect', () => @@ -996,7 +1029,7 @@ export default class RoomClient { logger.debug('signaling Peer "room-ready" event'); - this._joinRoom({ displayName, device }); + this._joinRoom(); }); this._signalingSocket.on('room-locked', () => @@ -1180,7 +1213,7 @@ export default class RoomClient }); } - async _joinRoom({ displayName, device }) + async _joinRoom() { logger.debug('_joinRoom()'); @@ -1236,7 +1269,13 @@ export default class RoomClient try { - await this._room.join(this._peerName, { displayName, device }); + await this._room.join( + this._peerName, + { + displayName : this._displayName, + device : this._device + } + ); store.dispatch( stateActions.setFileSharingSupported(this._torrentSupport)); @@ -1277,7 +1316,7 @@ export default class RoomClient if (this._produce) { if (this._room.canSend('audio')) - this._setMicProducer(); + await this._setMicProducer(); // Add our webcam (unless the cookie says no). if (this._room.canSend('video')) @@ -1285,7 +1324,7 @@ export default class RoomClient const devicesCookie = cookiesManager.getDevices(); if (!devicesCookie || devicesCookie.webcamEnabled) - this.enableWebcam(); + await this.enableWebcam(); } } @@ -1462,6 +1501,14 @@ export default class RoomClient store.dispatch(stateActions.setProducerVolume(producer.id, volume)); } }); + + this._audioContext = new AudioContext(); + + // We need to provoke user interaction to get permission from browser to start audio + if (this._audioContext.state === 'suspended') + { + store.dispatch(stateActions.setAudioSuspended({ audioSuspended: true })); + } } catch (error) { @@ -1472,7 +1519,6 @@ export default class RoomClient if (producer) producer.close(); - throw error; } } diff --git a/app/lib/components/Room.jsx b/app/lib/components/Room.jsx index 352b9df..0830733 100644 --- a/app/lib/components/Room.jsx +++ b/app/lib/components/Room.jsx @@ -1,5 +1,6 @@ import React, { Fragment } from 'react'; import { connect } from 'react-redux'; +import { withRoomContext } from '../RoomContext'; import ReactTooltip from 'react-tooltip'; import PropTypes from 'prop-types'; import classnames from 'classnames'; @@ -63,6 +64,7 @@ class Room extends React.Component render() { const { + roomClient, room, amActiveSpeaker, onRoomLinkCopy @@ -73,7 +75,31 @@ class Room extends React.Component democratic : Peers }[room.mode]; - if (room.lockedOut) + if (room.audioSuspended) + { + return ( + + + + + This webpage required sound and video to play, please click to allow. + + { + roomClient.notify('Joining.'); + roomClient.resumeAudio(); + }} + className='button' + > + Allow + + + + + + ); + } + else if (room.lockedOut) { return ( @@ -186,6 +212,7 @@ class Room extends React.Component Room.propTypes = { + roomClient : PropTypes.object.isRequired, room : appPropTypes.Room.isRequired, me : appPropTypes.Me.isRequired, amActiveSpeaker : PropTypes.bool.isRequired, @@ -228,9 +255,9 @@ const mapDispatchToProps = (dispatch) => }; }; -const RoomContainer = connect( +const RoomContainer = withRoomContext(connect( mapStateToProps, mapDispatchToProps -)(Room); +)(Room)); export default RoomContainer; diff --git a/app/lib/index.jsx b/app/lib/index.jsx index a74a8e4..68b4dd4 100644 --- a/app/lib/index.jsx +++ b/app/lib/index.jsx @@ -120,7 +120,7 @@ function run() // TODO: Debugging stuff. global.CLIENT = roomClient; -setInterval(() => +/* setInterval(() => { if (!roomClient._room.peers[0]) { @@ -133,7 +133,7 @@ setInterval(() => global.CONSUMER = peer.consumers[peer.consumers.length - 1]; }, 2000); - +*/ global.sendSdp = function() { logger.debug('---------- SEND_TRANSPORT LOCAL SDP OFFER:'); diff --git a/app/lib/redux/reducers/room.js b/app/lib/redux/reducers/room.js index a2a267e..e5e2ed6 100644 --- a/app/lib/redux/reducers/room.js +++ b/app/lib/redux/reducers/room.js @@ -4,6 +4,7 @@ const initialState = state : 'new', // new/connecting/connected/disconnected/closed, locked : false, lockedOut : false, + audioSuspended : false, activeSpeakerName : null, torrentSupport : false, showSettings : false, @@ -52,6 +53,13 @@ const room = (state = initialState, action) => return { ...state, lockedOut: true }; } + case 'SET_AUDIO_SUSPENDED': + { + const { audioSuspended } = action.payload; + + return { ...state, audioSuspended }; + } + case 'SET_ROOM_ACTIVE_SPEAKER': { const { peerName } = action.payload; diff --git a/app/lib/redux/stateActions.js b/app/lib/redux/stateActions.js index a797006..5ebf248 100644 --- a/app/lib/redux/stateActions.js +++ b/app/lib/redux/stateActions.js @@ -43,6 +43,14 @@ export const setRoomLockedOut = () => }; }; +export const setAudioSuspended = ({ audioSuspended }) => +{ + return { + type : 'SET_AUDIO_SUSPENDED', + payload : { audioSuspended } + }; +}; + export const setMe = ({ peerName, displayName, displayNameSet, device, loginEnabled }) => { return { diff --git a/app/stylus/components/Room.styl b/app/stylus/components/Room.styl index ee1371b..1d09abc 100644 --- a/app/stylus/components/Room.styl +++ b/app/stylus/components/Room.styl @@ -15,6 +15,34 @@ padding: 2vmin; } + > .sound-suspended { + position: fixed; + top: 50%; + left: 50%; + transform: translateX(-50%) translateY(-50%); + width: 30vw; + text-align: center; + font-family: 'Roboto'; + font-size: 2.5em; + box-shadow: 0px 5px 12px 2px rgba(17, 17, 17, 0.5); + background-color: #fff; + padding: 2vmin; + + > .button { + font-size: 1.5em; + padding: 1.5vmin; + margin: 0.8vmin; + background-color: #38cd8b; + border-radius: 1.8vmin; + color: #fff; + border: 0; + } + + > .button:hover { + background-color: #28bd7b; + } + } + > .room-wrapper { position: absolute; top: 0;