fix: VoIP calls one-way audio issues (#35420)

pull/35456/head
Pierre Lehnen 1 year ago committed by GitHub
parent c1439427a7
commit 21f413d6c8
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 5
      .changeset/real-mayflies-provide.md
  2. 53
      packages/ui-voip/src/lib/VoipClient.ts
  3. 19
      packages/ui-voip/src/providers/VoipProvider.tsx

@ -0,0 +1,5 @@
---
'@rocket.chat/ui-voip': patch
---
fixes an issue where audio from VoIP calls would sometimes not be played

@ -1,5 +1,4 @@
import type { IMediaStreamRenderer, SignalingSocketEvents, VoipEvents as CoreVoipEvents } from '@rocket.chat/core-typings';
import { type VoIPUserConfiguration } from '@rocket.chat/core-typings';
import type { SignalingSocketEvents, VoipEvents as CoreVoipEvents, VoIPUserConfiguration } from '@rocket.chat/core-typings';
import { Emitter } from '@rocket.chat/emitter';
import type { InvitationAcceptOptions, Message, Referral, Session, SessionInviteOptions } from 'sip.js';
import { Registerer, RequestPendingError, SessionState, UserAgent, Invitation, Inviter, RegistererState, UserAgentState } from 'sip.js';
@ -33,7 +32,7 @@ class VoipClient extends Emitter<VoipEvents> {
public networkEmitter: Emitter<SignalingSocketEvents>;
private mediaStreamRendered: IMediaStreamRenderer | undefined;
private audioElement: HTMLAudioElement | null = null;
private remoteStream: RemoteStream | undefined;
@ -47,11 +46,9 @@ class VoipClient extends Emitter<VoipEvents> {
private contactInfo: ContactInfo | null = null;
constructor(private readonly config: VoIPUserConfiguration, mediaRenderer?: IMediaStreamRenderer) {
constructor(private readonly config: VoIPUserConfiguration) {
super();
this.mediaStreamRendered = mediaRenderer;
this.networkEmitter = new Emitter<SignalingSocketEvents>();
}
@ -101,8 +98,8 @@ class VoipClient extends Emitter<VoipEvents> {
}
}
static async create(config: VoIPUserConfiguration, mediaRenderer?: IMediaStreamRenderer): Promise<VoipClient> {
const voip = new VoipClient(config, mediaRenderer);
static async create(config: VoIPUserConfiguration): Promise<VoipClient> {
const voip = new VoipClient(config);
await voip.init();
return voip;
}
@ -154,7 +151,7 @@ class VoipClient extends Emitter<VoipEvents> {
});
};
public call = async (calleeURI: string, mediaRenderer?: IMediaStreamRenderer): Promise<void> => {
public call = async (calleeURI: string): Promise<void> => {
if (!calleeURI) {
throw new Error('Invalid URI');
}
@ -167,10 +164,6 @@ class VoipClient extends Emitter<VoipEvents> {
throw new Error('No User Agent.');
}
if (mediaRenderer) {
this.switchMediaRenderer(mediaRenderer);
}
const target = this.makeURI(calleeURI);
if (!target) {
@ -441,14 +434,12 @@ class VoipClient extends Emitter<VoipEvents> {
return true;
}
public switchMediaRenderer(mediaRenderer: IMediaStreamRenderer): void {
if (!this.remoteStream) {
return;
}
public switchAudioElement(audioElement: HTMLAudioElement | null): void {
this.audioElement = audioElement;
this.mediaStreamRendered = mediaRenderer;
this.remoteStream.init(mediaRenderer.remoteMediaElement);
this.remoteStream.play();
if (this.remoteStream) {
this.playRemoteStream();
}
}
private setContactInfo(contact: ContactInfo) {
@ -613,6 +604,10 @@ class VoipClient extends Emitter<VoipEvents> {
};
}
public getAudioElement(): HTMLAudioElement | null {
return this.audioElement;
}
public notifyDialer(value: { open: boolean }) {
this.emit('dialer', value);
}
@ -633,12 +628,22 @@ class VoipClient extends Emitter<VoipEvents> {
const { remoteMediaStream } = this.sessionDescriptionHandler;
this.remoteStream = new RemoteStream(remoteMediaStream);
const mediaElement = this.mediaStreamRendered?.remoteMediaElement;
this.playRemoteStream();
}
if (mediaElement) {
this.remoteStream.init(mediaElement);
this.remoteStream.play();
private playRemoteStream() {
if (!this.remoteStream) {
console.warn(`Attempted to play missing remote media.`);
return;
}
if (!this.audioElement) {
console.error('Unable to play remote media: VoIPClient is missing an AudioElement reference to play it on.');
return;
}
this.remoteStream.init(this.audioElement);
this.remoteStream.play();
}
private makeURI(calleeURI: string): URI | undefined {

@ -8,7 +8,7 @@ import {
useToastMessageDispatch,
} from '@rocket.chat/ui-contexts';
import type { ReactNode } from 'react';
import { useEffect, useMemo, useRef } from 'react';
import { useCallback, useEffect, useMemo } from 'react';
import { createPortal } from 'react-dom';
import { useTranslation } from 'react-i18next';
@ -39,7 +39,12 @@ const VoipProvider = ({ children }: { children: ReactNode }) => {
const dispatchToastMessage = useToastMessageDispatch();
// Refs
const remoteAudioMediaRef = useRef<HTMLAudioElement>(null);
const remoteAudioMediaRef = useCallback(
(node: HTMLMediaElement | null) => {
voipClient?.switchAudioElement(node);
},
[voipClient],
);
useEffect(() => {
if (!voipClient) {
@ -54,10 +59,6 @@ const VoipProvider = ({ children }: { children: ReactNode }) => {
const onCallEstablished = async (): Promise<void> => {
voipSounds.stopAll();
window.addEventListener('beforeunload', onBeforeUnload);
if (voipClient.isCallee() && remoteAudioMediaRef.current) {
voipClient.switchMediaRenderer({ remoteMediaElement: remoteAudioMediaRef.current });
}
};
const onNetworkDisconnected = (): void => {
@ -120,11 +121,13 @@ const VoipProvider = ({ children }: { children: ReactNode }) => {
}, [dispatchToastMessage, setStorageRegistered, t, voipClient, voipSounds]);
const changeAudioOutputDevice = useEffectEvent(async (selectedAudioDevice: Device): Promise<void> => {
if (!remoteAudioMediaRef.current) {
const element = voipClient?.getAudioElement();
if (!element) {
console.warn(`Failed to change audio output device: missing audio element reference.`);
return;
}
setOutputMediaDevice({ outputDevice: selectedAudioDevice, HTMLAudioElement: remoteAudioMediaRef.current });
setOutputMediaDevice({ outputDevice: selectedAudioDevice, HTMLAudioElement: element });
});
const changeAudioInputDevice = useEffectEvent(async (selectedAudioDevice: Device): Promise<void> => {

Loading…
Cancel
Save