allowing for device selection with webrtc calls

This commit is contained in:
Roland Osborne 2024-03-05 16:09:17 -08:00
parent 9af0fc744f
commit 48baa779fb
11 changed files with 110 additions and 127 deletions

View File

@ -117,7 +117,6 @@ export const en = {
copied: 'Copied',
accounts: 'Accounts',
accessAccount: 'Access Account',
createAccount: 'Create Account',
browserLink: 'Browser Link',
mobileToken: 'Mobile Token',
createLink: 'Create Account Link',
@ -126,7 +125,6 @@ export const en = {
disableAccount: 'Disable Account',
enableAccount: 'Enable Account',
deleteAccount: 'Delete Account',
settings: 'Settings',
hostHint: 'domain:port/app',
federatedHost: 'Federated Host',
storageLimit: 'Storage Limit (GB) / Account',
@ -153,7 +151,6 @@ export const en = {
deleteMessage: 'Deleting Message',
messageHint: 'Are you sure you want to delete the message?',
newMessage: 'New Message',
attachImage: 'Attach Image',
attachVideo: 'Attach Video',
attachAudio: 'Attach Audio',
@ -301,7 +298,6 @@ export const fr = {
copied: 'Copié',
accounts: 'Comptes',
accessAccount: 'Accéder au Compte',
createAccount: 'Créer un Compte',
browserLink: 'Lien du Navigateur',
mobileToken: 'Code Mobile',
createLink: 'Lien pour Créer un Compte',
@ -310,7 +306,6 @@ export const fr = {
disableAccount: 'Désactiver le Compte',
enableAccount: 'Activer le Compte',
deleteAccount: 'Supprimer le Compte',
settings: 'Paramètres',
hostHint: 'domaine:port/app',
federatedHost: 'Hôte Fédéré',
storageLimit: 'Limite de Espace (Go) / Compte',
@ -337,7 +332,6 @@ export const fr = {
deleteMessage: 'Suppression du Message',
messageHint: 'Êtes-vous Sûr de Vouloir Supprimer le Message?',
newMessage: 'Nouveau Message',
attachImage: 'Joindre une Image',
attachVideo: 'Joindre une Vidéo',
attachAudio: 'Joindre un Audio',

View File

@ -134,8 +134,31 @@ export function useRingContext() {
processing.current = false;
}
const transmit = async (policy, ice) => {
const getAudioStream = async (audioId) => {
try {
if (audioId) {
return await navigator.mediaDevices.getUserMedia({ video: false, audio: { deviceId: audioId } });
}
}
catch (err) {
console.log(err);
}
return await navigator.mediaDevices.getUserMedia({ video: false, audio: true });
}
const getVideoStream = async (videoId) => {
try {
if (videoId) {
return await navigator.mediaDevices.getUserMedia({ video: { deviceId: videoId }, audio: false });
}
}
catch (err) {
console.log(err);
}
return await navigator.mediaDevices.getUserMedia({ video: true, audio: false });
}
const transmit = async (policy, ice, audioId) => {
pc.current = new RTCPeerConnection({ iceServers: ice });
pc.current.ontrack = (ev) => {
if (!stream.current) {
@ -164,13 +187,7 @@ export function useRingContext() {
};
try {
const devices = await navigator.mediaDevices.enumerateDevices();
console.log('>> ', devices);
const stream = await navigator.mediaDevices.getUserMedia({
video: false,
audio: true,
});
const stream = await getAudioStream(audioId);
accessAudio.current = true;
updateState({ localAudio: true });
for (const track of stream.getTracks()) {
@ -185,7 +202,7 @@ export function useRingContext() {
}
}
const connect = async (policy, node, token, clearRing, clearAlive, ice) => {
const connect = async (policy, audioId, node, token, clearRing, clearAlive, ice) => {
// connect signal socket
connected.current = false;
@ -209,7 +226,7 @@ export function useRingContext() {
updateState({ callStatus: "connected" });
if (policy === 'polite') {
connected.current = true;
transmit('polite', ice);
transmit('polite', ice, audioId);
polite();
}
}
@ -260,7 +277,7 @@ export function useRingContext() {
ws.current.send(JSON.stringify({ AppToken: token }));
if (policy === 'impolite') {
connected.current = true;
transmit('impolite', ice);
transmit('impolite', ice, audioId);
impolite();
}
}
@ -317,7 +334,7 @@ export function useRingContext() {
}
}
},
accept: async (cardId, callId, contactNode, contactToken, calleeToken, iceUrl, iceUsername, icePassword) => {
accept: async (cardId, callId, contactNode, contactToken, calleeToken, iceUrl, iceUsername, icePassword, audioId) => {
if (calling.current) {
throw new Error("active session");
}
@ -332,7 +349,7 @@ export function useRingContext() {
updateState({ ringing: ringing.current, callStatus: "connecting", cardId });
calling.current = { callId, contactNode, contactToken, host: false };
await connect('impolite', contactNode, calleeToken, () => {}, () => {}, ice);
await connect('impolite', audioId, contactNode, calleeToken, () => {}, () => {}, ice);
}
},
end: async () => {
@ -362,7 +379,7 @@ export function useRingContext() {
}
}
},
call: async (cardId, contactNode, contactToken) => {
call: async (cardId, contactNode, contactToken, audioId) => {
if (calling.current) {
throw new Error("active session");
}
@ -416,17 +433,11 @@ export function useRingContext() {
updateState({ callStatus: "ringing" });
calling.current = { callId: id, host: true };
const ice = [{ urls: iceUrl, username: iceUsername, credential: icePassword }];
await connect('polite', window.location.host, callerToken, () => clearInterval(ringInterval), () => clearInterval(aliveInterval), ice);
await connect('polite', audioId, window.location.host, callerToken, () => clearInterval(ringInterval), () => clearInterval(aliveInterval), ice);
},
enableVideo: async () => {
enableVideo: async (videoId) => {
if (!accessVideo.current) {
const devices = await navigator.mediaDevices.enumerateDevices();
console.log('>> ', devices);
const stream = await navigator.mediaDevices.getUserMedia({
video: true,
audio: true,
});
const stream = await getVideoStream(videoId);
accessVideo.current = true;
accessAudio.current = true;
updateState({ localStream: stream });

View File

@ -18,9 +18,9 @@ export function useSettingsContext() {
strings: en,
dateFormat: 'mm/dd',
timeFormat: '12h',
audioInput: null,
audioId: null,
audioInputs: [],
videoInput: null,
videoId: null,
videoInputs: [],
});
@ -75,6 +75,7 @@ export function useSettingsContext() {
getDevices('video').then(video => {
updateState({ videoInputs: video });
});
// eslint-disable-next-line
}, [state.strings]);
useEffect(() => {
@ -134,9 +135,9 @@ export function useSettingsContext() {
}
}
const audioInput = localStorage.getItem('audio_input');
const videoInput = localStorage.getItem('video_input');
updateState({ audioInput, videoInput });
const audioId = localStorage.getItem('audio_input');
const videoId = localStorage.getItem('video_input');
updateState({ audioId, videoId });
return () => {
window.removeEventListener('resize', handleResize);
@ -193,13 +194,13 @@ export function useSettingsContext() {
localStorage.setItem('time_format', timeFormat);
updateState({ timeFormat });
},
setAudioInput: (audioInput) => {
localStorage.setItem('audio_input', audioInput);
updateState({ audioInput });
setAudioInput: (audioId) => {
localStorage.setItem('audio_input', audioId);
updateState({ audioId });
},
setVideoInput: (videoInput) => {
localStorage.setItem('video_input', videoInput);
updateState({ videoInput });
setVideoInput: (videoId) => {
localStorage.setItem('video_input', videoId);
updateState({ videoId });
},
}

View File

@ -1,6 +1,6 @@
import { useRef, useCallback } from 'react';
import { Modal, Input, Button, Switch } from 'antd';
import { LogoutContent, ProfileWrapper, ProfileDetailsWrapper, ProfileImageWrapper } from './Profile.styled';
import { ProfileWrapper, ProfileDetailsWrapper, ProfileImageWrapper } from './Profile.styled';
import { useProfile } from './useProfile.hook';
import { Logo } from 'logo/Logo';
import { AccountAccess } from './accountAccess/AccountAccess';
@ -52,24 +52,6 @@ export function Profile({ closeProfile }) {
}
}
const logout = () => {
modal.confirm({
title: <span style={state.menuStyle}>{state.strings.confirmLogout}</span>,
icon: <LogoutOutlined />,
content: <LogoutContent onClick={(e) => e.stopPropagation()}>
<span className="logoutMode">{ state.strings.allDevices }</span>
<Switch onChange={(e) => all.current = e} size="small" />
</LogoutContent>,
bodyStyle: { borderRadius: 8, padding: 16, ...state.menuStyle },
okText: state.strings.ok,
onOk() {
actions.logout(all.current);
},
cancelText: state.strings.cancel,
onCancel() {},
});
}
const onCropComplete = useCallback((area, crop) => {
actions.setEditImageCrop(crop.width, crop.height, crop.x, crop.y)
// eslint-disable-next-line
@ -135,12 +117,6 @@ export function Profile({ closeProfile }) {
</div>
{ state.display !== 'xlarge' && state.displaySet && (
<div className="rightAccess">
{ state.display === 'small' && (
<div className="logout">
<LogoutOutlined className="icon" onClick={logout} />
<div className="label" onClick={logout}>{ state.strings.logout }</div>
</div>
)}
<AccountAccess />
<div className="contentFill" />
</div>

View File

@ -200,38 +200,6 @@ export const ProfileWrapper = styled.div`
padding: 8px;
width: 75%;
}
.logout {
display: flex;
flex-direction: row;
align-items: center;
background-color: ${props => props.theme.selectedArea};
padding: 8px;
border-radius: 4px;
justify-content: center;
.icon {
color: ${props => props.theme.alertText};
cursor: pointer;
}
.label {
cursor: pointer;
padding-left: 8px;
}
}
`
export const LogoutContent = styled.div`
display: flex;
align-items: center;
justify-content: center;
padding: 8px;
.logoutMode {
padding-right: 8px;
color: ${props => props.theme.mainText};
}
`
export const ProfileDetailsWrapper = styled.div`

View File

@ -1,12 +1,14 @@
import { AccountAccessWrapper, LoginModal, SealModal } from './AccountAccess.styled';
import { AccountAccessWrapper, LoginModal, SealModal, LogoutContent } from './AccountAccess.styled';
import { useAccountAccess } from './useAccountAccess.hook';
import { Button, Modal, Switch, Input, Radio, Select } from 'antd';
import { SettingOutlined, UserOutlined, LockOutlined, ExclamationCircleOutlined } from '@ant-design/icons';
import { LogoutOutlined, SettingOutlined, UserOutlined, LockOutlined, ExclamationCircleOutlined } from '@ant-design/icons';
import { useRef } from 'react';
export function AccountAccess() {
const [ modal, modalContext ] = Modal.useModal();
const { state, actions } = useAccountAccess();
const all = useRef(false);
const saveSeal = async () => {
try {
@ -52,6 +54,24 @@ export function AccountAccess() {
}
}
const logout = () => {
modal.confirm({
title: <span style={state.menuStyle}>{state.strings.confirmLogout}</span>,
icon: <LogoutOutlined />,
content: <LogoutContent onClick={(e) => e.stopPropagation()}>
<span className="logoutMode">{ state.strings.allDevices }</span>
<Switch onChange={(e) => all.current = e} size="small" />
</LogoutContent>,
bodyStyle: { borderRadius: 8, padding: 16, ...state.menuStyle },
okText: state.strings.ok,
onOk() {
actions.logout(all.current);
},
cancelText: state.strings.cancel,
onCancel() {},
});
}
return (
<AccountAccessWrapper>
{ modalContext }
@ -76,6 +96,14 @@ export function AccountAccess() {
</div>
<div className="label">{state.strings.changeLogin}</div>
</div>
{ state.display === 'small' && (
<div className="link" onClick={logout}>
<div className="control">
<LogoutOutlined className="icon" />
</div>
<div className="label">{ state.strings.logout }</div>
</div>
)}
</div>
</div>
<div className="account">
@ -123,7 +151,7 @@ export function AccountAccess() {
defaultValue={null}
style={{ width: '60%' }}
size="small"
value={state.audioInput}
value={state.audioId}
onChange={actions.setAudio}
options={[ { value: null, label: 'Default' }, ...state.audioInputs ]}
/>
@ -134,7 +162,7 @@ export function AccountAccess() {
defaultValue={null}
style={{ width: '60%' }}
size="small"
value={state.videoInput}
value={state.videoId}
onChange={actions.setVideo}
options={[ { value: null, label: 'Default' }, ...state.videoInputs ]}
/>

View File

@ -39,7 +39,7 @@ export const AccountAccessWrapper = styled.div`
.label {
padding-right: 16px;
min-width: 130px;
min-width: 33%;
height: 28px;
display: flex;
align-items: center;
@ -247,3 +247,14 @@ export const LoginModal = styled.div`
}
`
export const LogoutContent = styled.div`
display: flex;
align-items: center;
justify-content: center;
padding: 8px;
.logoutMode {
padding-right: 8px;
color: ${props => props.theme.mainText};
}
`

View File

@ -27,6 +27,7 @@ export function useAccountAccess() {
sealDelete: null,
sealUnlock: null,
display: null,
strings: {},
menuStyle: {},
timeFormat: '12h',
@ -35,9 +36,9 @@ export function useAccountAccess() {
themes: [],
language: null,
languages: [],
audioInput: null,
audioId: null,
audioInputs: [],
videoInput: null,
videoId: null,
videoInputs: [],
seal: null,
@ -65,21 +66,10 @@ export function useAccountAccess() {
}, [account.state]);
useEffect(() => {
const { audioInput, audioInputs, videoInput, videoInputs, strings, menuStyle, timeFormat, dateFormat, theme, themes, language, languages } = settings.state;
updateState({ audioInput, audioInputs, videoInput, videoInputs, strings, menuStyle, timeFormat, dateFormat, theme, themes, language, languages });
const { display, audioId, audioInputs, videoId, videoInputs, strings, menuStyle, timeFormat, dateFormat, theme, themes, language, languages } = settings.state;
updateState({ display, audioId, audioInputs, videoId, videoInputs, strings, menuStyle, timeFormat, dateFormat, theme, themes, language, languages });
}, [settings.state]);
const showDevices = async () => {
const audio = await ring.actions.getDevices('audio');
const video = await ring.actions.getDevices('video');
console.log('devices', audio, video);
};
useEffect(() => {
showDevices();
}, []);
const sealUnlock = async () => {
const unlocked = unlockSeal(state.seal, state.sealUnlock);
await account.actions.unlockSeal(unlocked);

View File

@ -21,6 +21,7 @@ export function useCards() {
menuStyle: {},
allowUnsealed: false,
cards: [],
audioId: null,
});
const ring = useContext(RingContext);
@ -35,8 +36,9 @@ export function useCards() {
}
useEffect(() => {
const { display, strings, menuStyle } = settings.state;
updateState({ display, strings, menuStyle });
const { display, strings, menuStyle, audioId } = settings.state;
console.log("AUDIO ID: ", audioId);
updateState({ display, strings, menuStyle, audioId });
}, [settings.state]);
useEffect(() => {
@ -164,7 +166,7 @@ export function useCards() {
},
call: async (contact) => {
const { cardId, node, guid, token } = contact;
await ring.actions.call(cardId, node, `${guid}.${token}`);
await ring.actions.call(cardId, node, `${guid}.${token}`, state.audioId);
},
};

View File

@ -53,8 +53,8 @@ export const AddTopicWrapper = styled.div`
flex-align: center;
justify-content: center;
align-items: center;
width: 36px;
height: 36px;
width: 32px;
height: 32px;
cursor: pointer;
border: 1px solid ${props => props.theme.sectionBorder};
background-color: ${props => props.theme.inputArea};
@ -63,7 +63,7 @@ export const AddTopicWrapper = styled.div`
}
.space {
margin-right: 8px;
margin-right: 12px;
}
.end {

View File

@ -32,6 +32,8 @@ export function useSession() {
remoteStream: null,
remoteVideo: false,
remoteAudio: false,
audioId: null,
videoId: null,
});
const app = useContext(AppContext);
@ -96,8 +98,8 @@ export function useSession() {
}, [app.state]);
useEffect(() => {
const { display, theme } = settings.state;
updateState({ display, theme });
const { display, theme, audioId } = settings.state;
updateState({ display, theme, audioId });
}, [settings.state]);
useEffect(() => {
@ -171,13 +173,13 @@ export function useSession() {
accept: async (call) => {
const { cardId, callId, contactNode, contactToken, calleeToken, iceUrl, iceUsername, icePassword } = call;
const node = contactNode ? contactNode : window.location.host;
await ring.actions.accept(cardId, callId, node, contactToken, calleeToken, iceUrl, iceUsername, icePassword);
await ring.actions.accept(cardId, callId, node, contactToken, calleeToken, iceUrl, iceUsername, icePassword, state.audioId);
},
end: async () => {
await ring.actions.end();
},
enableVideo: async () => {
await ring.actions.enableVideo();
await ring.actions.enableVideo(state.videoId, state.audioId);
},
disableVideo: async () => {
await ring.actions.disableVideo();