add a way to choose the audioouput/mute a webrtc call

pull/2039/head
Audric Ackermann 4 years ago
parent 53289298a9
commit a4daabfa75
No known key found for this signature in database
GPG Key ID: 999F434D76324AD4

@ -1,6 +1,6 @@
import { SessionIconButton } from '../icon'; import { SessionIconButton } from '../icon';
import { animation, contextMenu, Item, Menu } from 'react-contexify'; import { animation, contextMenu, Item, Menu } from 'react-contexify';
import { InputItem } from '../../../session/utils/CallManager'; import { InputItem } from '../../../session/utils/calling/CallManager';
import { setFullScreenCall } from '../../../state/ducks/call'; import { setFullScreenCall } from '../../../state/ducks/call';
import { CallManager, ToastUtils } from '../../../session/utils'; import { CallManager, ToastUtils } from '../../../session/utils';
import React from 'react'; import React from 'react';
@ -71,16 +71,16 @@ export const AudioInputButton = ({
export const AudioOutputButton = ({ export const AudioOutputButton = ({
currentConnectedAudioOutputs, currentConnectedAudioOutputs,
}: // isAudioOutputMuted, isAudioOutputMuted,
// hideArrowIcon = false, hideArrowIcon = false,
{ }: {
currentConnectedAudioOutputs: Array<InputItem>; currentConnectedAudioOutputs: Array<InputItem>;
isAudioOutputMuted: boolean; isAudioOutputMuted: boolean;
hideArrowIcon?: boolean; hideArrowIcon?: boolean;
}) => { }) => {
return ( return (
<> <>
{/* <DropDownAndToggleButton <DropDownAndToggleButton
iconType="volume" iconType="volume"
isMuted={isAudioOutputMuted} isMuted={isAudioOutputMuted}
onMainButtonClick={() => { onMainButtonClick={() => {
@ -90,7 +90,7 @@ export const AudioOutputButton = ({
showAudioOutputMenu(currentConnectedAudioOutputs, e); showAudioOutputMenu(currentConnectedAudioOutputs, e);
}} }}
hidePopoverArrow={hideArrowIcon} hidePopoverArrow={hideArrowIcon}
/> */} />
<AudioOutputMenu <AudioOutputMenu
triggerId={audioOutputTriggerId} triggerId={audioOutputTriggerId}
@ -238,19 +238,19 @@ const showAudioInputMenu = (
}); });
}; };
// const showAudioOutputMenu = ( const showAudioOutputMenu = (
// currentConnectedAudioOutputs: Array<any>, currentConnectedAudioOutputs: Array<any>,
// e: React.MouseEvent<HTMLDivElement> e: React.MouseEvent<HTMLDivElement>
// ) => { ) => {
// if (currentConnectedAudioOutputs.length === 0) { if (currentConnectedAudioOutputs.length === 0) {
// ToastUtils.pushNoAudioOutputFound(); ToastUtils.pushNoAudioOutputFound();
// return; return;
// } }
// contextMenu.show({ contextMenu.show({
// id: audioOutputTriggerId, id: audioOutputTriggerId,
// event: e, event: e,
// }); });
// }; };
const showVideoInputMenu = ( const showVideoInputMenu = (
currentConnectedCameras: Array<InputItem>, currentConnectedCameras: Array<InputItem>,
@ -300,22 +300,22 @@ const handleMicrophoneToggle = async (
} }
}; };
// const handleSpeakerToggle = async ( const handleSpeakerToggle = async (
// currentConnectedAudioOutputs: Array<InputItem>, currentConnectedAudioOutputs: Array<InputItem>,
// isAudioOutputMuted: boolean isAudioOutputMuted: boolean
// ) => { ) => {
// if (!currentConnectedAudioOutputs.length) { if (!currentConnectedAudioOutputs.length) {
// ToastUtils.pushNoAudioInputFound(); ToastUtils.pushNoAudioInputFound();
// return; return;
// } }
// if (isAudioOutputMuted) { if (isAudioOutputMuted) {
// // selects the first one // selects the first one
// await CallManager.selectAudioOutputByDeviceId(currentConnectedAudioOutputs[0].deviceId); await CallManager.selectAudioOutputByDeviceId(currentConnectedAudioOutputs[0].deviceId);
// } else { } else {
// await CallManager.selectAudioOutputByDeviceId(CallManager.DEVICE_DISABLED_DEVICE_ID); await CallManager.selectAudioOutputByDeviceId(CallManager.DEVICE_DISABLED_DEVICE_ID);
// } }
// }; };
const StyledCallWindowControls = styled.div` const StyledCallWindowControls = styled.div`
position: absolute; position: absolute;

@ -23,7 +23,7 @@ import {
import { useModuloWithTripleDots } from '../../../hooks/useModuloWithTripleDots'; import { useModuloWithTripleDots } from '../../../hooks/useModuloWithTripleDots';
import { CallWindowControls } from './CallButtons'; import { CallWindowControls } from './CallButtons';
import { SessionSpinner } from '../SessionSpinner'; import { SessionSpinner } from '../SessionSpinner';
import { DEVICE_DISABLED_DEVICE_ID } from '../../../session/utils/CallManager'; import { DEVICE_DISABLED_DEVICE_ID } from '../../../session/utils/calling/CallManager';
const VideoContainer = styled.div` const VideoContainer = styled.div`
height: 100%; height: 100%;
@ -156,10 +156,10 @@ export const InConversationCallContainer = () => {
if (videoRefRemote.current) { if (videoRefRemote.current) {
if (currentSelectedAudioOutput === DEVICE_DISABLED_DEVICE_ID) { if (currentSelectedAudioOutput === DEVICE_DISABLED_DEVICE_ID) {
videoRefLocal.current.muted = true; videoRefRemote.current.muted = true;
} else { } else {
// void videoRefLocal.current.setSinkId(currentSelectedAudioOutput); // void videoRefRemote.current.setSinkId(currentSelectedAudioOutput);
videoRefLocal.current.muted = false; videoRefRemote.current.muted = false;
} }
} }
} }

@ -2,12 +2,13 @@ import { useEffect, useState } from 'react';
import { useSelector } from 'react-redux'; import { useSelector } from 'react-redux';
// tslint:disable-next-line: no-submodule-imports // tslint:disable-next-line: no-submodule-imports
import useMountedState from 'react-use/lib/useMountedState'; import useMountedState from 'react-use/lib/useMountedState';
import { CallManager } from '../session/utils';
import { import {
addVideoEventsListener,
CallManagerOptionsType, CallManagerOptionsType,
DEVICE_DISABLED_DEVICE_ID, DEVICE_DISABLED_DEVICE_ID,
InputItem, InputItem,
} from '../session/utils/CallManager'; removeVideoEventsListener,
} from '../session/utils/calling/CallManager';
import { getSelectedConversationKey } from '../state/selectors/conversations'; import { getSelectedConversationKey } from '../state/selectors/conversations';
import { getCallIsInFullScreen, getHasOngoingCallWithPubkey } from '../state/selectors/call'; import { getCallIsInFullScreen, getHasOngoingCallWithPubkey } from '../state/selectors/call';
@ -40,7 +41,7 @@ export function useVideoCallEventsListener(uniqueId: string, onSame: boolean) {
(onSame && ongoingCallPubkey === selectedConversationKey) || (onSame && ongoingCallPubkey === selectedConversationKey) ||
(!onSame && ongoingCallPubkey !== selectedConversationKey) (!onSame && ongoingCallPubkey !== selectedConversationKey)
) { ) {
CallManager.addVideoEventsListener(uniqueId, (options: CallManagerOptionsType) => { addVideoEventsListener(uniqueId, (options: CallManagerOptionsType) => {
const { const {
audioInputsList, audioInputsList,
audioOutputsList, audioOutputsList,
@ -68,7 +69,7 @@ export function useVideoCallEventsListener(uniqueId: string, onSame: boolean) {
} }
return () => { return () => {
CallManager.removeVideoEventsListener(uniqueId); removeVideoEventsListener(uniqueId);
}; };
}, [ongoingCallPubkey, selectedConversationKey, isFullScreen]); }, [ongoingCallPubkey, selectedConversationKey, isFullScreen]);

@ -1,10 +1,10 @@
import _ from 'lodash'; import _ from 'lodash';
import { MessageUtils, ToastUtils, UserUtils } from '.'; import { MessageUtils, ToastUtils, UserUtils } from '../';
import { getCallMediaPermissionsSettings } from '../../components/session/settings/SessionSettings'; import { getCallMediaPermissionsSettings } from '../../../components/session/settings/SessionSettings';
import { getConversationById } from '../../data/data'; import { getConversationById } from '../../../data/data';
import { MessageModelType } from '../../models/messageType'; import { MessageModelType } from '../../../models/messageType';
import { SignalService } from '../../protobuf'; import { SignalService } from '../../../protobuf';
import { openConversationWithMessages } from '../../state/ducks/conversations'; import { openConversationWithMessages } from '../../../state/ducks/conversations';
import { import {
answerCall, answerCall,
callConnected, callConnected,
@ -13,22 +13,23 @@ import {
incomingCall, incomingCall,
setFullScreenCall, setFullScreenCall,
startingCallWith, startingCallWith,
} from '../../state/ducks/call'; } from '../../../state/ducks/call';
import { getConversationController } from '../conversations'; import { getConversationController } from '../../conversations';
import { CallMessage } from '../messages/outgoing/controlMessage/CallMessage'; import { CallMessage } from '../../messages/outgoing/controlMessage/CallMessage';
import { ed25519Str } from '../onions/onionPath'; import { ed25519Str } from '../../onions/onionPath';
import { getMessageQueue, MessageSender } from '../sending'; import { PubKey } from '../../types';
import { PubKey } from '../types';
import { v4 as uuidv4 } from 'uuid'; import { v4 as uuidv4 } from 'uuid';
import { PnServer } from '../../pushnotification'; import { PnServer } from '../../../pushnotification';
import { setIsRinging } from './RingingManager'; import { setIsRinging } from '../RingingManager';
import { getBlackSilenceMediaStream } from './Silence';
import { getMessageQueue } from '../..';
import { MessageSender } from '../../sending';
export type InputItem = { deviceId: string; label: string };
// tslint:disable: function-name // tslint:disable: function-name
const maxWidth = 1920; export type InputItem = { deviceId: string; label: string };
const maxHeight = 1080;
/** /**
* This uuid is set only once we accepted a call or started one. * This uuid is set only once we accepted a call or started one.
*/ */
@ -169,28 +170,6 @@ if (typeof navigator !== 'undefined') {
}); });
} }
const silence = () => {
const ctx = new AudioContext();
const oscillator = ctx.createOscillator();
const dst = oscillator.connect(ctx.createMediaStreamDestination());
oscillator.start();
return Object.assign((dst as any).stream.getAudioTracks()[0], { enabled: false });
};
const black = () => {
const canvas = Object.assign(document.createElement('canvas'), {
width: maxWidth,
height: maxHeight,
});
canvas.getContext('2d')?.fillRect(0, 0, maxWidth, maxHeight);
const stream = (canvas as any).captureStream();
return Object.assign(stream.getVideoTracks()[0], { enabled: false });
};
const getBlackSilenceMediaStream = () => {
return new MediaStream([black(), silence()]);
};
async function updateConnectedDevices() { async function updateConnectedDevices() {
// Get the set of cameras connected // Get the set of cameras connected
const videoCameras = await getConnectedDevices('videoinput'); const videoCameras = await getConnectedDevices('videoinput');
@ -339,12 +318,13 @@ export async function selectAudioInputByDeviceId(audioInputDeviceId: string) {
if (!peerConnection) { if (!peerConnection) {
throw new Error('cannot selectAudioInputByDeviceId without a peer connection'); throw new Error('cannot selectAudioInputByDeviceId without a peer connection');
} }
const sender = peerConnection.getSenders().find(s => { const audioSender = peerConnection.getSenders().find(s => {
return s.track?.kind === audioTrack.kind; return s.track?.kind === audioTrack.kind;
}); });
window.log.info('replacing audio track');
if (sender) { if (audioSender) {
await sender.replaceTrack(audioTrack); await audioSender.replaceTrack(audioTrack);
// we actually do not need to toggle the track here, as toggling it here unmuted here locally (so we start to hear ourselves) // we actually do not need to toggle the track here, as toggling it here unmuted here locally (so we start to hear ourselves)
} else { } else {
throw new Error('Failed to get sender for selectAudioInputByDeviceId '); throw new Error('Failed to get sender for selectAudioInputByDeviceId ');
@ -562,6 +542,11 @@ function handleConnectionStateChanged(pubkey: string) {
if (firstAudioInput) { if (firstAudioInput) {
void selectAudioInputByDeviceId(firstAudioInput); void selectAudioInputByDeviceId(firstAudioInput);
} }
const firstAudioOutput = audioOutputsList?.[0].deviceId || undefined;
if (firstAudioOutput) {
void selectAudioOutputByDeviceId(firstAudioOutput);
}
window.inboxStore?.dispatch(callConnected({ pubkey })); window.inboxStore?.dispatch(callConnected({ pubkey }));
} }
} }

@ -0,0 +1,24 @@
const maxWidth = 1920;
const maxHeight = 1080;
const silence = () => {
const ctx = new AudioContext();
const oscillator = ctx.createOscillator();
const dst = oscillator.connect(ctx.createMediaStreamDestination());
oscillator.start();
return Object.assign((dst as any).stream.getAudioTracks()[0], { enabled: false });
};
const black = () => {
const canvas = Object.assign(document.createElement('canvas'), {
width: maxWidth,
height: maxHeight,
});
canvas.getContext('2d')?.fillRect(0, 0, maxWidth, maxHeight);
const stream = (canvas as any).captureStream();
return Object.assign(stream.getVideoTracks()[0], { enabled: false });
};
export const getBlackSilenceMediaStream = () => {
return new MediaStream([black(), silence()]);
};

@ -8,7 +8,7 @@ import * as UserUtils from './User';
import * as SyncUtils from './syncUtils'; import * as SyncUtils from './syncUtils';
import * as AttachmentsV2Utils from './AttachmentsV2'; import * as AttachmentsV2Utils from './AttachmentsV2';
import * as AttachmentDownloads from './AttachmentsDownload'; import * as AttachmentDownloads from './AttachmentsDownload';
import * as CallManager from './CallManager'; import * as CallManager from './calling/CallManager';
export * from './Attachments'; export * from './Attachments';
export * from './TypedEmitter'; export * from './TypedEmitter';

Loading…
Cancel
Save