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'}
videoTrack={webcamConsumer && webcamConsumer.track}
videoVisible={videoVisible}
audioTrack={micConsumer && micConsumer.track}
audioCodec={micConsumer && micConsumer.codec}
videoCodec={webcamConsumer && webcamConsumer.codec}
audioScore={micConsumer ? micConsumer.score : null}

View File

@ -1,6 +1,6 @@
import React from 'react';
import { connect } from 'react-redux';
import { micConsumerSelector } from '../Selectors';
import { passiveMicConsumerSelector } from '../Selectors';
import PropTypes from 'prop-types';
import PeerAudio from './PeerAudio';
@ -37,7 +37,7 @@ AudioPeers.propTypes =
const mapStateToProps = (state) =>
({
micConsumers : micConsumerSelector(state),
micConsumers : passiveMicConsumerSelector(state),
audioOutputDevice : state.settings.selectedAudioOutputDevice
});
@ -50,7 +50,9 @@ const AudioPeersContainer = connect(
{
return (
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')
);
export const passiveMicConsumerSelector = createSelector(
spotlightsSelector,
consumersSelect,
(spotlights, consumers) =>
Object.values(consumers).filter(
(consumer) => consumer.source === 'mic' && !spotlights.includes(consumer.peerId)
)
);
export const spotlightsLengthSelector = createSelector(
spotlightsSelector,
(spotlights) => spotlights.length

View File

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