Handle mid-call output changes

Signed-off-by: Šimon Brandner <simon.bra.ag@gmail.com>
This commit is contained in:
Šimon Brandner 2021-06-23 09:56:37 +02:00
parent 9bceb40820
commit 58151d71c5
No known key found for this signature in database
GPG key ID: 9760693FDD98A790
3 changed files with 40 additions and 11 deletions

View file

@ -18,6 +18,7 @@ limitations under the License.
import SettingsStore from "./settings/SettingsStore"; import SettingsStore from "./settings/SettingsStore";
import { SettingLevel } from "./settings/SettingLevel"; import { SettingLevel } from "./settings/SettingLevel";
import { setMatrixCallAudioInput, setMatrixCallVideoInput } from "matrix-js-sdk/src/matrix"; import { setMatrixCallAudioInput, setMatrixCallVideoInput } from "matrix-js-sdk/src/matrix";
import EventEmitter from 'events';
interface IMediaDevices { interface IMediaDevices {
audioOutput: Array<MediaDeviceInfo>; audioOutput: Array<MediaDeviceInfo>;
@ -25,7 +26,22 @@ interface IMediaDevices {
videoInput: Array<MediaDeviceInfo>; videoInput: Array<MediaDeviceInfo>;
} }
export default class MediaDeviceHandler { export enum MediaDeviceHandlerEvent {
AudioOutputChanged = "audio_output_changed",
AudioInputChanged = "audio_input_changed",
VideoInputChanged = "video_input_changed",
}
export default class MediaDeviceHandler extends EventEmitter {
private static internalInstance;
public static get instance(): MediaDeviceHandler {
if (!MediaDeviceHandler.internalInstance) {
MediaDeviceHandler.internalInstance = new MediaDeviceHandler();
}
return MediaDeviceHandler.internalInstance;
}
static async hasAnyLabeledDevices(): Promise<boolean> { static async hasAnyLabeledDevices(): Promise<boolean> {
const devices = await navigator.mediaDevices.enumerateDevices(); const devices = await navigator.mediaDevices.enumerateDevices();
return devices.some(d => Boolean(d.label)); return devices.some(d => Boolean(d.label));
@ -68,18 +84,21 @@ export default class MediaDeviceHandler {
setMatrixCallVideoInput(videoDeviceId); setMatrixCallVideoInput(videoDeviceId);
} }
static setAudioOutput(deviceId: string) { public setAudioOutput(deviceId: string) {
SettingsStore.setValue("webrtc_audiooutput", null, SettingLevel.DEVICE, deviceId); SettingsStore.setValue("webrtc_audiooutput", null, SettingLevel.DEVICE, deviceId);
this.emit(MediaDeviceHandlerEvent.AudioOutputChanged, deviceId);
} }
static setAudioInput(deviceId: string) { public setAudioInput(deviceId: string) {
SettingsStore.setValue("webrtc_audioinput", null, SettingLevel.DEVICE, deviceId); SettingsStore.setValue("webrtc_audioinput", null, SettingLevel.DEVICE, deviceId);
setMatrixCallAudioInput(deviceId); setMatrixCallAudioInput(deviceId);
this.emit(MediaDeviceHandlerEvent.AudioInputChanged, deviceId);
} }
static setVideoInput(deviceId: string) { public setVideoInput(deviceId: string) {
SettingsStore.setValue("webrtc_videoinput", null, SettingLevel.DEVICE, deviceId); SettingsStore.setValue("webrtc_videoinput", null, SettingLevel.DEVICE, deviceId);
setMatrixCallVideoInput(deviceId); setMatrixCallVideoInput(deviceId);
this.emit(MediaDeviceHandlerEvent.VideoInputChanged, deviceId);
} }
static getAudioOutput(): string { static getAudioOutput(): string {

View file

@ -100,21 +100,21 @@ export default class VoiceUserSettingsTab extends React.Component {
}; };
_setAudioOutput = (e) => { _setAudioOutput = (e) => {
MediaDeviceHandler.setAudioOutput(e.target.value); MediaDeviceHandler.instance.setAudioOutput(e.target.value);
this.setState({ this.setState({
activeAudioOutput: e.target.value, activeAudioOutput: e.target.value,
}); });
}; };
_setAudioInput = (e) => { _setAudioInput = (e) => {
MediaDeviceHandler.setAudioInput(e.target.value); MediaDeviceHandler.instance.setAudioInput(e.target.value);
this.setState({ this.setState({
activeAudioInput: e.target.value, activeAudioInput: e.target.value,
}); });
}; };
_setVideoInput = (e) => { _setVideoInput = (e) => {
MediaDeviceHandler.setVideoInput(e.target.value); MediaDeviceHandler.instance.setVideoInput(e.target.value);
this.setState({ this.setState({
activeVideoInput: e.target.value, activeVideoInput: e.target.value,
}); });

View file

@ -17,7 +17,7 @@ limitations under the License.
import React, {createRef} from 'react'; import React, {createRef} from 'react';
import { CallFeed, CallFeedEvent } from 'matrix-js-sdk/src/webrtc/callFeed'; import { CallFeed, CallFeedEvent } from 'matrix-js-sdk/src/webrtc/callFeed';
import { logger } from 'matrix-js-sdk/src/logger'; import { logger } from 'matrix-js-sdk/src/logger';
import MediaDeviceHandler from "../../../MediaDeviceHandler"; import MediaDeviceHandler, { MediaDeviceHandlerEvent } from "../../../MediaDeviceHandler";
interface IProps { interface IProps {
feed: CallFeed, feed: CallFeed,
@ -27,19 +27,25 @@ export default class AudioFeed extends React.Component<IProps> {
private element = createRef<HTMLAudioElement>(); private element = createRef<HTMLAudioElement>();
componentDidMount() { componentDidMount() {
MediaDeviceHandler.instance.addListener(
MediaDeviceHandlerEvent.AudioOutputChanged,
this.onAudioOutputChanged,
);
this.props.feed.addListener(CallFeedEvent.NewStream, this.onNewStream); this.props.feed.addListener(CallFeedEvent.NewStream, this.onNewStream);
this.playMedia(); this.playMedia();
} }
componentWillUnmount() { componentWillUnmount() {
MediaDeviceHandler.instance.removeListener(
MediaDeviceHandlerEvent.AudioOutputChanged,
this.onAudioOutputChanged,
);
this.props.feed.removeListener(CallFeedEvent.NewStream, this.onNewStream); this.props.feed.removeListener(CallFeedEvent.NewStream, this.onNewStream);
this.stopMedia(); this.stopMedia();
} }
private playMedia() { private onAudioOutputChanged = (audioOutput: string) => {
const element = this.element.current; const element = this.element.current;
const audioOutput = MediaDeviceHandler.getAudioOutput();
if (audioOutput) { if (audioOutput) {
try { try {
// 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
@ -52,7 +58,11 @@ export default class AudioFeed extends React.Component<IProps> {
logger.warn("Couldn't set requested audio output device: using default", e); logger.warn("Couldn't set requested audio output device: using default", e);
} }
} }
}
private playMedia() {
const element = this.element.current;
this.onAudioOutputChanged(MediaDeviceHandler.getAudioOutput());
element.muted = false; element.muted = false;
element.srcObject = this.props.feed.stream; element.srcObject = this.props.feed.stream;
element.autoplay = true; element.autoplay = true;