diff --git a/res/css/views/voip/_CallView.scss b/res/css/views/voip/_CallView.scss index 63ca91267f..aa0aa4e2a6 100644 --- a/res/css/views/voip/_CallView.scss +++ b/res/css/views/voip/_CallView.scss @@ -74,9 +74,9 @@ limitations under the License. > .mx_VideoFeed { width: 100%; height: 100%; + border-width: 0 !important; // Override mx_VideoFeed_speaking &.mx_VideoFeed_voice { - background-color: $inverted-bg-color; display: flex; justify-content: center; align-items: center; diff --git a/res/css/views/voip/_VideoFeed.scss b/res/css/views/voip/_VideoFeed.scss index 7a8d39dfe3..ec7260a8bb 100644 --- a/res/css/views/voip/_VideoFeed.scss +++ b/res/css/views/voip/_VideoFeed.scss @@ -17,12 +17,17 @@ limitations under the License. .mx_VideoFeed { overflow: hidden; position: relative; + box-sizing: border-box; &.mx_VideoFeed_voice { background-color: $inverted-bg-color; aspect-ratio: 16 / 9; } + &.mx_VideoFeed_speaking { + border: $accent-color 2px solid; + } + .mx_VideoFeed_video { width: 100%; background-color: transparent; diff --git a/src/components/views/voip/VideoFeed.tsx b/src/components/views/voip/VideoFeed.tsx index 4607b750eb..0d719a4463 100644 --- a/src/components/views/voip/VideoFeed.tsx +++ b/src/components/views/voip/VideoFeed.tsx @@ -24,6 +24,8 @@ import MemberAvatar from "../avatars/MemberAvatar"; import { replaceableComponent } from "../../../utils/replaceableComponent"; import { SDPStreamMetadataPurpose } from 'matrix-js-sdk/src/webrtc/callEventTypes'; +const SPEAKING_THRESHOLD = -60; + interface IProps { call: MatrixCall; @@ -45,6 +47,7 @@ interface IProps { interface IState { audioMuted: boolean; videoMuted: boolean; + speaking: boolean; } @replaceableComponent("views.voip.VideoFeed") @@ -57,6 +60,7 @@ export default class VideoFeed extends React.PureComponent { this.state = { audioMuted: this.props.feed.isAudioMuted(), videoMuted: this.props.feed.isVideoMuted(), + speaking: false, }; } @@ -103,11 +107,19 @@ export default class VideoFeed extends React.PureComponent { if (oldFeed) { this.props.feed.removeListener(CallFeedEvent.NewStream, this.onNewStream); 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(); } if (newFeed) { this.props.feed.addListener(CallFeedEvent.NewStream, this.onNewStream); 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(); } } @@ -162,6 +174,10 @@ export default class VideoFeed extends React.PureComponent { }); }; + private onVolumeChanged = (volume: number): void => { + this.setState({ speaking: volume > SPEAKING_THRESHOLD }); + }; + private onResize = (e) => { if (this.props.onResize && !this.props.feed.isLocal()) { this.props.onResize(e); @@ -173,6 +189,7 @@ export default class VideoFeed extends React.PureComponent { const wrapperClasses = classnames("mx_VideoFeed", { mx_VideoFeed_voice: this.state.videoMuted, + mx_VideoFeed_speaking: this.state.speaking, }); const micIconClasses = classnames("mx_VideoFeed_mic", { mx_VideoFeed_mic_muted: this.state.audioMuted,