1
0
mirror of https://github.com/matrix-org/matrix-react-sdk.git synced 2025-07-28 15:22:05 +03:00

Handle audio

Signed-off-by: Šimon Brandner <simon.bra.ag@gmail.com>
This commit is contained in:
Šimon Brandner
2021-03-10 08:31:01 +01:00
parent ab60c9b5da
commit 8410411236
3 changed files with 52 additions and 48 deletions

View File

@ -137,21 +137,6 @@ export enum PlaceCallType {
ScreenSharing = 'screensharing', ScreenSharing = 'screensharing',
} }
function getRemoteAudioElement(): HTMLAudioElement {
// this needs to be somewhere at the top of the DOM which
// always exists to avoid audio interruptions.
// Might as well just use DOM.
const remoteAudioElement = document.getElementById("remoteAudio") as HTMLAudioElement;
if (!remoteAudioElement) {
console.error(
"Failed to find remoteAudio element - cannot play audio!" +
"You need to add an <audio/> to the DOM.",
);
return null;
}
return remoteAudioElement;
}
export default class CallHandler { export default class CallHandler {
private calls = new Map<string, MatrixCall>(); // roomId -> call private calls = new Map<string, MatrixCall>(); // roomId -> call
private audioPromises = new Map<AudioID, Promise<void>>(); private audioPromises = new Map<AudioID, Promise<void>>();
@ -538,11 +523,6 @@ export default class CallHandler {
} }
} }
private setCallAudioElement(call: MatrixCall) {
const audioElement = getRemoteAudioElement();
if (audioElement) call.setRemoteAudioElement(audioElement);
}
private setCallState(call: MatrixCall, status: CallState) { private setCallState(call: MatrixCall, status: CallState) {
const mappedRoomId = CallHandler.roomIdForCall(call); const mappedRoomId = CallHandler.roomIdForCall(call);
@ -635,7 +615,6 @@ export default class CallHandler {
this.calls.set(roomId, call); this.calls.set(roomId, call);
this.setCallListeners(call); this.setCallListeners(call);
this.setCallAudioElement(call);
this.setActiveCallRoomId(roomId); this.setActiveCallRoomId(roomId);
@ -787,7 +766,6 @@ export default class CallHandler {
const call = this.calls.get(payload.room_id); const call = this.calls.get(payload.room_id);
call.answer(); call.answer();
this.setCallAudioElement(call);
this.setActiveCallRoomId(payload.room_id); this.setActiveCallRoomId(payload.room_id);
CountlyAnalytics.instance.trackJoinCall(payload.room_id, call.type === CallType.Video, false); CountlyAnalytics.instance.trackJoinCall(payload.room_id, call.type === CallType.Video, false);
dis.dispatch({ dis.dispatch({

View File

@ -50,18 +50,15 @@ export default {
}, },
loadDevices: function() { loadDevices: function() {
const audioOutDeviceId = SettingsStore.getValue("webrtc_audiooutput");
const audioDeviceId = SettingsStore.getValue("webrtc_audioinput"); const audioDeviceId = SettingsStore.getValue("webrtc_audioinput");
const videoDeviceId = SettingsStore.getValue("webrtc_videoinput"); const videoDeviceId = SettingsStore.getValue("webrtc_videoinput");
Matrix.setMatrixCallAudioOutput(audioOutDeviceId);
Matrix.setMatrixCallAudioInput(audioDeviceId); Matrix.setMatrixCallAudioInput(audioDeviceId);
Matrix.setMatrixCallVideoInput(videoDeviceId); Matrix.setMatrixCallVideoInput(videoDeviceId);
}, },
setAudioOutput: function(deviceId) { setAudioOutput: function(deviceId) {
SettingsStore.setValue("webrtc_audiooutput", null, SettingLevel.DEVICE, deviceId); SettingsStore.setValue("webrtc_audiooutput", null, SettingLevel.DEVICE, deviceId);
Matrix.setMatrixCallAudioOutput(deviceId);
}, },
setAudioInput: function(deviceId) { setAudioInput: function(deviceId) {

View File

@ -23,6 +23,7 @@ import { MatrixClientPeg } from '../../../MatrixClientPeg';
import { logger } from 'matrix-js-sdk/src/logger'; import { logger } from 'matrix-js-sdk/src/logger';
import MemberAvatar from "../avatars/MemberAvatar" import MemberAvatar from "../avatars/MemberAvatar"
import CallHandler from '../../../CallHandler'; import CallHandler from '../../../CallHandler';
import CallMediaHandler from "../../../CallMediaHandler";
interface IProps { interface IProps {
call: MatrixCall, call: MatrixCall,
@ -45,7 +46,8 @@ interface IState {
} }
export default class VideoFeed extends React.Component<IProps, IState> { export default class VideoFeed extends React.Component<IProps, IState> {
private vid = createRef<HTMLVideoElement>(); private video = createRef<HTMLVideoElement>();
private audio = createRef<HTMLAudioElement>();
constructor(props: IProps) { constructor(props: IProps) {
super(props); super(props);
@ -57,38 +59,64 @@ 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);
if (!this.vid.current) return;
// A note on calling methods on media elements: const audioOutput = CallMediaHandler.getAudioOutput();
// We used to have queues per media element to serialise all calls on those elements. const currentMedia = this.getCurrentMedia();
// The reason given for this was that load() and play() were racing. However, we now
// never call load() explicitly so this seems unnecessary. However, serialising every currentMedia.srcObject = this.props.feed.stream;
// operation was causing bugs where video would not resume because some play command currentMedia.autoplay = true;
// had got stuck and all media operations were queued up behind it. If necessary, we currentMedia.muted = false;
// 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
// load() explicitly, it shouldn't be a problem. - Dave
this.vid.current.srcObject = this.props.feed.stream;
this.vid.current.autoplay = true;
this.vid.current.muted = true;
try { try {
this.vid.current.play(); if (audioOutput) {
// This seems quite unreliable in Chrome, although I haven't yet managed to make a jsfiddle where
// it fails.
// 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
currentMedia.setSinkId(audioOutput);
}
} catch (e) { } catch (e) {
logger.info("Failed to play video element with feed", this.props.feed, 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);
}
try {
// A note on calling methods on media elements:
// We used to have queues per media element to serialise all calls on those elements.
// The reason given for this was that load() and play() were racing. However, we now
// never call load() explicitly so this seems unnecessary. However, serialising every
// operation was causing bugs where video would not resume because some play command
// had got stuck and all media operations were queued up behind it. If necessary, we
// 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
// load() explicitly, it shouldn't be a problem. - Dave
currentMedia.play()
} catch (e) {
logger.info("Failed to play media element with feed", this.props.feed, e);
} }
} }
componentWillUnmount() { componentWillUnmount() {
this.props.feed.removeListener(CallFeedEvent.NewStream, this.onNewStream); this.props.feed.removeListener(CallFeedEvent.NewStream, this.onNewStream);
if (!this.vid.current) return; this.video.current?.removeEventListener('resize', this.onResize);
this.vid.current.removeEventListener('resize', this.onResize);
this.vid.current.pause(); const currentMedia = this.getCurrentMedia();
this.vid.current.srcObject = null; currentMedia.pause();
currentMedia.srcObject = null;
// As per comment in componentDidMount, setting the sink ID back to the
// default once the call is over makes setSinkId work reliably. - Dave
// Since we are not using the same element anymore, the above doesn't
// seem to be necessary - Šimon
}
getCurrentMedia() {
return this.audio.current || this.video.current;
} }
onNewStream = (newStream: MediaStream) => { onNewStream = (newStream: MediaStream) => {
this.setState({ audioOnly: this.props.feed.isAudioOnly()}); this.setState({ audioOnly: this.props.feed.isAudioOnly()});
if (!this.vid.current) return; const currentMedia = this.getCurrentMedia();
this.vid.current.srcObject = newStream; currentMedia.srcObject = newStream;
} }
onResize = (e) => { onResize = (e) => {
@ -123,11 +151,12 @@ export default class VideoFeed extends React.Component<IProps, IState> {
height={avatarSize} height={avatarSize}
width={avatarSize} width={avatarSize}
/> />
<audio ref={this.audio}></audio>
</div> </div>
); );
} else { } else {
return ( return (
<video className={classnames(videoClasses)} ref={this.vid} /> <video className={classnames(videoClasses)} ref={this.video} />
); );
} }
} }