Rework how media element are handled

Signed-off-by: Šimon Brandner <simon.bra.ag@gmail.com>
This commit is contained in:
Šimon Brandner 2021-04-23 19:41:55 +02:00
parent c54aa86532
commit fbb8cfb188
No known key found for this signature in database
GPG key ID: 9760693FDD98A790
2 changed files with 51 additions and 32 deletions

View file

@ -22,6 +22,8 @@ limitations under the License.
.mx_VideoFeed_video { .mx_VideoFeed_video {
background-color: #000; background-color: #000;
width: 100%;
height: 100%;
} }
.mx_VideoFeed_remote { .mx_VideoFeed_remote {

View file

@ -62,32 +62,40 @@ export default class VideoFeed extends React.Component<IProps, IState> {
componentDidMount() { componentDidMount() {
this.props.feed.addListener(CallFeedEvent.NewStream, this.onNewStream); this.props.feed.addListener(CallFeedEvent.NewStream, this.onNewStream);
this.playAllMedia();
this.playMedia();
} }
private playMedia() { componentWillUnmount() {
this.props.feed.removeListener(CallFeedEvent.NewStream, this.onNewStream);
this.video.current?.removeEventListener('resize', this.onResize);
this.stopAllMedia();
}
private playMediaElement(element: HTMLVideoElement | HTMLAudioElement) {
if (element instanceof HTMLAudioElement) {
const audioOutput = CallMediaHandler.getAudioOutput(); const audioOutput = CallMediaHandler.getAudioOutput();
const currentMedia = this.getCurrentMedia();
currentMedia.srcObject = this.props.feed.stream;
currentMedia.autoplay = true;
// Don't play audio if the feed is local // Don't play audio if the feed is local
currentMedia.muted = this.props.feed.isLocal(); element.muted = this.props.feed.isLocal();
if (audioOutput && !element.muted) {
try { try {
if (audioOutput) {
// This seems quite unreliable in Chrome, although I haven't yet managed to make a jsfiddle where // This seems quite unreliable in Chrome, although I haven't yet managed to make a jsfiddle where
// it fails. // it fails.
// It seems reliable if you set the sink ID after setting the srcObject and then set the sink ID // It seems reliable if you set the sink ID after setting the srcObject and then set the sink ID
// back to the default after the call is over - Dave // back to the default after the call is over - Dave
currentMedia.setSinkId(audioOutput); element.setSinkId(audioOutput);
}
} catch (e) { } catch (e) {
console.error("Couldn't set requested audio output device: using default", e); console.error("Couldn't set requested audio output device: using default", e);
logger.warn("Couldn't set requested audio output device: using default", e); logger.warn("Couldn't set requested audio output device: using default", e);
} }
}
} else {
element.muted = true;
}
element.srcObject = this.props.feed.stream;
element.autoplay = true;
try { try {
// A note on calling methods on media elements: // A note on calling methods on media elements:
// We used to have queues per media element to serialise all calls on those elements. // We used to have queues per media element to serialise all calls on those elements.
@ -98,27 +106,30 @@ export default class VideoFeed extends React.Component<IProps, IState> {
// should serialise the ones that need to be serialised but then be able to interrupt // should serialise the ones that need to be serialised but then be able to interrupt
// them with another load() which will cancel the pending one, but since we don't call // them with another load() which will cancel the pending one, but since we don't call
// load() explicitly, it shouldn't be a problem. - Dave // load() explicitly, it shouldn't be a problem. - Dave
currentMedia.play() element.play()
} catch (e) { } catch (e) {
logger.info("Failed to play media element with feed", this.props.feed, e); logger.info("Failed to play media element with feed", this.props.feed, e);
} }
} }
componentWillUnmount() { private stopMediaElement(element: HTMLAudioElement | HTMLVideoElement) {
this.props.feed.removeListener(CallFeedEvent.NewStream, this.onNewStream); element.pause();
this.video.current?.removeEventListener('resize', this.onResize); element.src = null;
const currentMedia = this.getCurrentMedia();
currentMedia.pause();
currentMedia.srcObject = null;
// As per comment in componentDidMount, setting the sink ID back to the // As per comment in componentDidMount, setting the sink ID back to the
// default once the call is over makes setSinkId work reliably. - Dave // default once the call is over makes setSinkId work reliably. - Dave
// Since we are not using the same element anymore, the above doesn't // Since we are not using the same element anymore, the above doesn't
// seem to be necessary - Šimon // seem to be necessary - Šimon
} }
private getCurrentMedia() { private playAllMedia() {
return this.audio.current || this.video.current; this.playMediaElement(this.audio.current);
if (this.video.current) this.playMediaElement(this.video.current);
}
private stopAllMedia() {
this.stopMediaElement(this.audio.current)
if (this.video.current) this.stopMediaElement(this.video.current);
} }
private onNewStream = () => { private onNewStream = () => {
@ -126,7 +137,7 @@ export default class VideoFeed extends React.Component<IProps, IState> {
audioMuted: this.props.feed.isAudioMuted(), audioMuted: this.props.feed.isAudioMuted(),
videoMuted: this.props.feed.isVideoMuted(), videoMuted: this.props.feed.isVideoMuted(),
}); });
this.playMedia(); this.playAllMedia();
}; };
private onResize = (e) => { private onResize = (e) => {
@ -141,13 +152,16 @@ export default class VideoFeed extends React.Component<IProps, IState> {
mx_VideoFeed_local: this.props.feed.isLocal(), mx_VideoFeed_local: this.props.feed.isLocal(),
mx_VideoFeed_remote: !this.props.feed.isLocal(), mx_VideoFeed_remote: !this.props.feed.isLocal(),
mx_VideoFeed_voice: this.state.videoMuted, mx_VideoFeed_voice: this.state.videoMuted,
mx_VideoFeed_video: !this.state.videoMuted,
mx_VideoFeed_mirror: ( mx_VideoFeed_mirror: (
this.props.feed.isLocal() && this.props.feed.isLocal() &&
SettingsStore.getValue('VideoView.flipVideoHorizontally') SettingsStore.getValue('VideoView.flipVideoHorizontally')
), ),
}; };
const audio = (
<audio ref={this.audio} />
);
if (this.state.videoMuted) { if (this.state.videoMuted) {
const member = this.props.feed.getMember(); const member = this.props.feed.getMember();
const avatarSize = this.props.pipMode ? 76 : 160; const avatarSize = this.props.pipMode ? 76 : 160;
@ -159,12 +173,15 @@ export default class VideoFeed extends React.Component<IProps, IState> {
height={avatarSize} height={avatarSize}
width={avatarSize} width={avatarSize}
/> />
<audio ref={this.audio}></audio> {audio}
</div> </div>
); );
} else { } else {
return ( return (
<video className={classnames(videoClasses)} ref={this.video} /> <div className={classnames(videoClasses)}>
<video className="mx_VideoFeed_video" ref={this.video} />
{audio}
</div>
); );
} }
} }