Add a speaking indicator

Signed-off-by: Šimon Brandner <simon.bra.ag@gmail.com>
This commit is contained in:
Šimon Brandner 2021-08-19 11:05:08 +02:00
parent 5a1633d53c
commit abab31c33b
No known key found for this signature in database
GPG Key ID: 55C211A1226CB17D
3 changed files with 23 additions and 1 deletions

View File

@ -74,9 +74,9 @@ limitations under the License.
> .mx_VideoFeed { > .mx_VideoFeed {
width: 100%; width: 100%;
height: 100%; height: 100%;
border-width: 0 !important; // Override mx_VideoFeed_speaking
&.mx_VideoFeed_voice { &.mx_VideoFeed_voice {
background-color: $inverted-bg-color;
display: flex; display: flex;
justify-content: center; justify-content: center;
align-items: center; align-items: center;

View File

@ -17,12 +17,17 @@ limitations under the License.
.mx_VideoFeed { .mx_VideoFeed {
overflow: hidden; overflow: hidden;
position: relative; position: relative;
box-sizing: border-box;
&.mx_VideoFeed_voice { &.mx_VideoFeed_voice {
background-color: $inverted-bg-color; background-color: $inverted-bg-color;
aspect-ratio: 16 / 9; aspect-ratio: 16 / 9;
} }
&.mx_VideoFeed_speaking {
border: $accent-color 2px solid;
}
.mx_VideoFeed_video { .mx_VideoFeed_video {
width: 100%; width: 100%;
background-color: transparent; background-color: transparent;

View File

@ -24,6 +24,8 @@ import MemberAvatar from "../avatars/MemberAvatar";
import { replaceableComponent } from "../../../utils/replaceableComponent"; import { replaceableComponent } from "../../../utils/replaceableComponent";
import { SDPStreamMetadataPurpose } from 'matrix-js-sdk/src/webrtc/callEventTypes'; import { SDPStreamMetadataPurpose } from 'matrix-js-sdk/src/webrtc/callEventTypes';
const SPEAKING_THRESHOLD = -60;
interface IProps { interface IProps {
call: MatrixCall; call: MatrixCall;
@ -45,6 +47,7 @@ interface IProps {
interface IState { interface IState {
audioMuted: boolean; audioMuted: boolean;
videoMuted: boolean; videoMuted: boolean;
speaking: boolean;
} }
@replaceableComponent("views.voip.VideoFeed") @replaceableComponent("views.voip.VideoFeed")
@ -57,6 +60,7 @@ export default class VideoFeed extends React.PureComponent<IProps, IState> {
this.state = { this.state = {
audioMuted: this.props.feed.isAudioMuted(), audioMuted: this.props.feed.isAudioMuted(),
videoMuted: this.props.feed.isVideoMuted(), videoMuted: this.props.feed.isVideoMuted(),
speaking: false,
}; };
} }
@ -103,11 +107,19 @@ export default class VideoFeed extends React.PureComponent<IProps, IState> {
if (oldFeed) { if (oldFeed) {
this.props.feed.removeListener(CallFeedEvent.NewStream, this.onNewStream); this.props.feed.removeListener(CallFeedEvent.NewStream, this.onNewStream);
this.props.feed.removeListener(CallFeedEvent.MuteStateChanged, this.onMuteStateChanged); this.props.feed.removeListener(CallFeedEvent.MuteStateChanged, this.onMuteStateChanged);
if (this.props.feed.purpose === SDPStreamMetadataPurpose.Usermedia) {
this.props.feed.removeListener(CallFeedEvent.VolumeChanged, this.onVolumeChanged);
this.props.feed.measureVolumeActivity(false);
}
this.stopMedia(); this.stopMedia();
} }
if (newFeed) { if (newFeed) {
this.props.feed.addListener(CallFeedEvent.NewStream, this.onNewStream); this.props.feed.addListener(CallFeedEvent.NewStream, this.onNewStream);
this.props.feed.addListener(CallFeedEvent.MuteStateChanged, this.onMuteStateChanged); this.props.feed.addListener(CallFeedEvent.MuteStateChanged, this.onMuteStateChanged);
if (this.props.feed.purpose === SDPStreamMetadataPurpose.Usermedia) {
this.props.feed.addListener(CallFeedEvent.VolumeChanged, this.onVolumeChanged);
this.props.feed.measureVolumeActivity(true);
}
this.playMedia(); this.playMedia();
} }
} }
@ -162,6 +174,10 @@ export default class VideoFeed extends React.PureComponent<IProps, IState> {
}); });
}; };
private onVolumeChanged = (volume: number): void => {
this.setState({ speaking: volume > SPEAKING_THRESHOLD });
};
private onResize = (e) => { private onResize = (e) => {
if (this.props.onResize && !this.props.feed.isLocal()) { if (this.props.onResize && !this.props.feed.isLocal()) {
this.props.onResize(e); this.props.onResize(e);
@ -173,6 +189,7 @@ export default class VideoFeed extends React.PureComponent<IProps, IState> {
const wrapperClasses = classnames("mx_VideoFeed", { const wrapperClasses = classnames("mx_VideoFeed", {
mx_VideoFeed_voice: this.state.videoMuted, mx_VideoFeed_voice: this.state.videoMuted,
mx_VideoFeed_speaking: this.state.speaking,
}); });
const micIconClasses = classnames("mx_VideoFeed_mic", { const micIconClasses = classnames("mx_VideoFeed_mic", {
mx_VideoFeed_mic_muted: this.state.audioMuted, mx_VideoFeed_mic_muted: this.state.audioMuted,