Play audio from video elements, fixes #242

auto_join_3.3
Håvar Aambø Fosstveit 2020-04-30 22:42:47 +02:00
parent 5c4eeb121a
commit 7a3bd327f2
4 changed files with 89 additions and 24 deletions

View File

@ -340,6 +340,7 @@ const Peer = (props) =>
videoMultiLayer={webcamConsumer && webcamConsumer.type !== 'simple'} videoMultiLayer={webcamConsumer && webcamConsumer.type !== 'simple'}
videoTrack={webcamConsumer && webcamConsumer.track} videoTrack={webcamConsumer && webcamConsumer.track}
videoVisible={videoVisible} videoVisible={videoVisible}
audioTrack={micConsumer && micConsumer.track}
audioCodec={micConsumer && micConsumer.codec} audioCodec={micConsumer && micConsumer.codec}
videoCodec={webcamConsumer && webcamConsumer.codec} videoCodec={webcamConsumer && webcamConsumer.codec}
audioScore={micConsumer ? micConsumer.score : null} audioScore={micConsumer ? micConsumer.score : null}

View File

@ -1,6 +1,6 @@
import React from 'react'; import React from 'react';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { micConsumerSelector } from '../Selectors'; import { passiveMicConsumerSelector } from '../Selectors';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import PeerAudio from './PeerAudio'; import PeerAudio from './PeerAudio';
@ -37,7 +37,7 @@ AudioPeers.propTypes =
const mapStateToProps = (state) => const mapStateToProps = (state) =>
({ ({
micConsumers : micConsumerSelector(state), micConsumers : passiveMicConsumerSelector(state),
audioOutputDevice : state.settings.selectedAudioOutputDevice audioOutputDevice : state.settings.selectedAudioOutputDevice
}); });
@ -50,7 +50,9 @@ const AudioPeersContainer = connect(
{ {
return ( return (
prev.consumers === next.consumers && prev.consumers === next.consumers &&
prev.settings.selectedAudioOutputDevice === next.settings.selectedAudioOutputDevice prev.room.spotlights === next.room.spotlights &&
prev.settings.selectedAudioOutputDevice ===
next.settings.selectedAudioOutputDevice
); );
} }
} }

View File

@ -67,6 +67,15 @@ export const screenConsumerSelector = createSelector(
(consumers) => Object.values(consumers).filter((consumer) => consumer.source === 'screen') (consumers) => Object.values(consumers).filter((consumer) => consumer.source === 'screen')
); );
export const passiveMicConsumerSelector = createSelector(
spotlightsSelector,
consumersSelect,
(spotlights, consumers) =>
Object.values(consumers).filter(
(consumer) => consumer.source === 'mic' && !spotlights.includes(consumer.peerId)
)
);
export const spotlightsLengthSelector = createSelector( export const spotlightsLengthSelector = createSelector(
spotlightsSelector, spotlightsSelector,
(spotlights) => spotlights.length (spotlights) => spotlights.length

View File

@ -3,6 +3,7 @@ import PropTypes from 'prop-types';
import classnames from 'classnames'; import classnames from 'classnames';
import { withStyles } from '@material-ui/core/styles'; import { withStyles } from '@material-ui/core/styles';
import EditableInput from '../Controls/EditableInput'; import EditableInput from '../Controls/EditableInput';
import Logger from '../../Logger';
import { green, yellow, orange, red } from '@material-ui/core/colors'; import { green, yellow, orange, red } from '@material-ui/core/colors';
import SignalCellularOffIcon from '@material-ui/icons/SignalCellularOff'; import SignalCellularOffIcon from '@material-ui/icons/SignalCellularOff';
import SignalCellular0BarIcon from '@material-ui/icons/SignalCellular0Bar'; import SignalCellular0BarIcon from '@material-ui/icons/SignalCellular0Bar';
@ -11,6 +12,8 @@ import SignalCellular2BarIcon from '@material-ui/icons/SignalCellular2Bar';
import SignalCellular3BarIcon from '@material-ui/icons/SignalCellular3Bar'; import SignalCellular3BarIcon from '@material-ui/icons/SignalCellular3Bar';
import SignalCellularAltIcon from '@material-ui/icons/SignalCellularAlt'; import SignalCellularAltIcon from '@material-ui/icons/SignalCellularAlt';
const logger = new Logger('VideoView');
const styles = (theme) => const styles = (theme) =>
({ ({
root : root :
@ -134,6 +137,10 @@ class VideoView extends React.PureComponent
videoHeight : null videoHeight : null
}; };
// Latest received audio track
// @type {MediaStreamTrack}
this._audioTrack = null;
// Latest received video track. // Latest received video track.
// @type {MediaStreamTrack} // @type {MediaStreamTrack}
this._videoTrack = null; this._videoTrack = null;
@ -292,7 +299,7 @@ class VideoView extends React.PureComponent
</div> </div>
<video <video
ref='video' ref='videoElement'
className={classnames(classes.video, { className={classnames(classes.video, {
hidden : !videoVisible, hidden : !videoVisible,
'isMe' : isMe && !isScreen, 'isMe' : isMe && !isScreen,
@ -300,6 +307,16 @@ class VideoView extends React.PureComponent
})} })}
autoPlay autoPlay
playsInline playsInline
muted
controls={false}
/>
<audio
ref='audioElement'
autoPlay
playsInline
muted={isMe}
controls={false}
/> />
{children} {children}
@ -309,52 +326,84 @@ class VideoView extends React.PureComponent
componentDidMount() componentDidMount()
{ {
const { videoTrack } = this.props; const { videoTrack, audioTrack } = this.props;
this._setTracks(videoTrack); this._setTracks(videoTrack, audioTrack);
} }
componentWillUnmount() componentWillUnmount()
{ {
clearInterval(this._videoResolutionTimer); clearInterval(this._videoResolutionTimer);
const { videoElement } = this.refs;
if (videoElement)
{
videoElement.oncanplay = null;
videoElement.onplay = null;
videoElement.onpause = null;
}
} }
// eslint-disable-next-line camelcase componentWillUpdate()
UNSAFE_componentWillReceiveProps(nextProps)
{ {
const { videoTrack } = nextProps; const { videoTrack, audioTrack } = this.props;
this._setTracks(videoTrack);
this._setTracks(videoTrack, audioTrack);
} }
_setTracks(videoTrack) _setTracks(videoTrack, audioTrack)
{ {
if (this._videoTrack === videoTrack) if (this._videoTrack === videoTrack && this._audioTrack === audioTrack)
return; return;
this._videoTrack = videoTrack; this._videoTrack = videoTrack;
this._audioTrack = audioTrack;
clearInterval(this._videoResolutionTimer); clearInterval(this._videoResolutionTimer);
this._hideVideoResolution(); this._hideVideoResolution();
const { video } = this.refs; const { videoElement, audioElement } = this.refs;
if (videoTrack) if (videoTrack)
{ {
const stream = new MediaStream(); const stream = new MediaStream();
if (videoTrack)
stream.addTrack(videoTrack); stream.addTrack(videoTrack);
video.srcObject = stream; videoElement.srcObject = stream;
videoElement.oncanplay = () => this.setState({ videoCanPlay: true });
videoElement.onplay = () =>
{
audioElement.play()
.catch((error) => logger.warn('audioElement.play() [error:"%o]', error));
};
videoElement.play()
.catch((error) => logger.warn('videoElement.play() [error:"%o]', error));
if (videoTrack)
this._showVideoResolution(); this._showVideoResolution();
} }
else else
{ {
video.srcObject = null; videoElement.srcObject = null;
}
if (audioTrack)
{
const stream = new MediaStream();
stream.addTrack(audioTrack);
audioElement.srcObject = stream;
audioElement.play()
.catch((error) => logger.warn('audioElement.play() [error:"%o]', error));
}
else
{
audioElement.srcObject = null;
} }
} }
@ -363,16 +412,19 @@ class VideoView extends React.PureComponent
this._videoResolutionTimer = setInterval(() => this._videoResolutionTimer = setInterval(() =>
{ {
const { videoWidth, videoHeight } = this.state; const { videoWidth, videoHeight } = this.state;
const { video } = this.refs; const { videoElement } = this.refs;
// Don't re-render if nothing changed. // Don't re-render if nothing changed.
if (video.videoWidth === videoWidth && video.videoHeight === videoHeight) if (
videoElement.videoWidth === videoWidth &&
videoElement.videoHeight === videoHeight
)
return; return;
this.setState( this.setState(
{ {
videoWidth : video.videoWidth, videoWidth : videoElement.videoWidth,
videoHeight : video.videoHeight videoHeight : videoElement.videoHeight
}); });
}, 1000); }, 1000);
} }
@ -392,6 +444,7 @@ VideoView.propTypes =
videoContain : PropTypes.bool, videoContain : PropTypes.bool,
advancedMode : PropTypes.bool, advancedMode : PropTypes.bool,
videoTrack : PropTypes.any, videoTrack : PropTypes.any,
audioTrack : PropTypes.any,
videoVisible : PropTypes.bool.isRequired, videoVisible : PropTypes.bool.isRequired,
consumerSpatialLayers : PropTypes.number, consumerSpatialLayers : PropTypes.number,
consumerTemporalLayers : PropTypes.number, consumerTemporalLayers : PropTypes.number,