selecting webrtc device

This commit is contained in:
Roland Osborne 2024-03-04 21:57:38 -08:00
parent 0b4e7da468
commit 9af0fc744f
10 changed files with 167 additions and 31 deletions

View File

@ -176,6 +176,10 @@ export const en = {
editMembership: 'Edit Membership',
deleteTopic: 'Delete Topic',
leaveTopic: 'Leave Topic',
integrated: 'Integrated',
microphone: 'Microphone',
camera: 'Camera'
};
export const fr = {
@ -356,5 +360,9 @@ export const fr = {
editMembership: 'Modifier Membres du Suject',
deleteTopic: 'Supprimer le Sujet',
leaveTopic: 'Quitter le Suject',
integrated: 'Intégré',
microphone: 'Microphone',
camera: 'Caméra'
};

View File

@ -164,6 +164,9 @@ export function useRingContext() {
};
try {
const devices = await navigator.mediaDevices.enumerateDevices();
console.log('>> ', devices);
const stream = await navigator.mediaDevices.getUserMedia({
video: false,
audio: true,
@ -417,6 +420,9 @@ export function useRingContext() {
},
enableVideo: async () => {
if (!accessVideo.current) {
const devices = await navigator.mediaDevices.enumerateDevices();
console.log('>> ', devices);
const stream = await navigator.mediaDevices.getUserMedia({
video: true,
audio: true,
@ -457,6 +463,24 @@ export function useRingContext() {
updateState({ localAudio: false });
}
},
getDevices: async (type) => {
const filtered = new Map();
const devices = await navigator.mediaDevices.enumerateDevices();
devices.filter(item => item.kind === type + 'input').forEach(item => {
if (item && item.label) {
const entry = filtered.get(item.groupId);
if (entry) {
if (item.label && item.label.length < entry.label.length) {
filtered.set(item.groupId, item);
}
}
else {
filtered.set(item.groupId, item);
}
}
});
return Array.from(filtered.values());
},
}
return { state, actions }

View File

@ -18,6 +18,10 @@ export function useSettingsContext() {
strings: en,
dateFormat: 'mm/dd',
timeFormat: '12h',
audioInput: null,
audioInputs: [],
videoInput: null,
videoInputs: [],
});
const SMALL_MEDIUM = 650;
@ -43,6 +47,36 @@ export function useSettingsContext() {
}
};
const getDevices = async (type) => {
const filtered = new Map();
const devices = await navigator.mediaDevices.enumerateDevices();
devices.filter(item => item.kind === type + 'input').forEach(item => {
if (item) {
const label = item.label ? item.label : state.strings.integrated;
const entry = filtered.get(item.groupId);
if (entry) {
if (item.label && label.length < entry.label.length) {
filtered.set(item.groupId, { value: item.deviceId, label });
}
}
else {
filtered.set(item.groupId, { value: item.deviceId, label });
}
}
});
return Array.from(filtered.values());
}
useEffect(() => {
getDevices('audio').then(audio => {
updateState({ audioInputs: audio });
});
getDevices('video').then(video => {
updateState({ videoInputs: video });
});
}, [state.strings]);
useEffect(() => {
for (let i = 0; i < 10; i++) {
setTimeout(handleResize, 100 * i); //cludge for my mobile browser
@ -100,6 +134,10 @@ export function useSettingsContext() {
}
}
const audioInput = localStorage.getItem('audio_input');
const videoInput = localStorage.getItem('video_input');
updateState({ audioInput, videoInput });
return () => {
window.removeEventListener('resize', handleResize);
window.removeEventListener('orientationchange', handleResize);
@ -155,6 +193,14 @@ export function useSettingsContext() {
localStorage.setItem('time_format', timeFormat);
updateState({ timeFormat });
},
setAudioInput: (audioInput) => {
localStorage.setItem('audio_input', audioInput);
updateState({ audioInput });
},
setVideoInput: (videoInput) => {
localStorage.setItem('video_input', videoInput);
updateState({ videoInput });
},
}
return { state, actions }

View File

@ -135,13 +135,13 @@ export function Profile({ closeProfile }) {
</div>
{ state.display !== 'xlarge' && state.displaySet && (
<div className="rightAccess">
<AccountAccess />
{ state.display === 'small' && (
<div className="logout" onClick={logout}>
<LogoutOutlined />
<div className="label">{ state.strings.logout }</div>
<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

@ -205,14 +205,18 @@ export const ProfileWrapper = styled.div`
display: flex;
flex-direction: row;
align-items: center;
cursor: pointer;
color: ${props => props.theme.mainText};
background-color: ${props => props.theme.modalArea};
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;
}
}

View File

@ -55,6 +55,29 @@ export function AccountAccess() {
return (
<AccountAccessWrapper>
{ modalContext }
<div className="account">
<div className="section">{state.strings.account}</div>
<div className="controls">
<div className="switch">
<div className="control">
<Switch size="small" checked={state.searchable} onChange={enable => saveSearchable(enable)} />
</div>
<div className="switchLabel">{state.strings.registry}</div>
</div>
<div className="link" onClick={actions.setEditSeal}>
<div className="control">
<SettingOutlined />
</div>
<div className="label">{state.strings.sealedTopics}</div>
</div>
<div className="link" onClick={actions.setEditLogin}>
<div className="control">
<LockOutlined />
</div>
<div className="label">{state.strings.changeLogin}</div>
</div>
</div>
</div>
<div className="account">
<div className="section">{state.strings.application}</div>
<div className="controls">
@ -94,28 +117,27 @@ export function AccountAccess() {
options={state.languages}
/>
</div>
</div>
</div>
<div className="account">
<div className="section">{state.strings.account}</div>
<div className="controls">
<div className="switch">
<div className="control">
<Switch size="small" checked={state.searchable} onChange={enable => saveSearchable(enable)} />
</div>
<div className="switchLabel">{state.strings.registry}</div>
<div className="option">
<div className="label">{state.strings.microphone}</div>
<Select
defaultValue={null}
style={{ width: '60%' }}
size="small"
value={state.audioInput}
onChange={actions.setAudio}
options={[ { value: null, label: 'Default' }, ...state.audioInputs ]}
/>
</div>
<div className="link" onClick={actions.setEditSeal}>
<div className="control">
<SettingOutlined />
</div>
<div className="label">{state.strings.sealedTopics}</div>
</div>
<div className="link" onClick={actions.setEditLogin}>
<div className="control">
<LockOutlined />
</div>
<div className="label">{state.strings.changeLogin}</div>
<div className="option">
<div className="label">{state.strings.camera}</div>
<Select
defaultValue={null}
style={{ width: '60%' }}
size="small"
value={state.videoInput}
onChange={actions.setVideo}
options={[ { value: null, label: 'Default' }, ...state.videoInputs ]}
/>
</div>
</div>
</div>

View File

@ -35,6 +35,7 @@ export const AccountAccessWrapper = styled.div`
display: flex;
padding-top: 8px;
align-items: center;
width: 100%;
.label {
padding-right: 16px;

View File

@ -2,6 +2,7 @@ import { useRef, useState, useEffect, useContext } from 'react';
import { AccountContext } from 'context/AccountContext';
import { ProfileContext } from 'context/ProfileContext';
import { SettingsContext } from 'context/SettingsContext';
import { RingContext } from 'context/RingContext';
import { generateSeal, unlockSeal, updateSeal } from 'context/sealUtil';
import { getUsername } from 'api/getUsername';
export function useAccountAccess() {
@ -34,6 +35,10 @@ export function useAccountAccess() {
themes: [],
language: null,
languages: [],
audioInput: null,
audioInputs: [],
videoInput: null,
videoInputs: [],
seal: null,
sealKey: null,
@ -42,6 +47,7 @@ export function useAccountAccess() {
const profile = useContext(ProfileContext);
const account = useContext(AccountContext);
const settings = useContext(SettingsContext);
const ring = useContext(RingContext);
const debounce = useRef(null);
const updateState = (value) => {
@ -59,10 +65,21 @@ export function useAccountAccess() {
}, [account.state]);
useEffect(() => {
const { strings, menuStyle, timeFormat, dateFormat, theme, themes, language, languages } = settings.state;
updateState({ strings, menuStyle, timeFormat, dateFormat, theme, themes, language, languages });
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 });
}, [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);
@ -113,6 +130,12 @@ export function useAccountAccess() {
setLanguage: (language) => {
settings.actions.setLanguage(language);
},
setAudio: (device) => {
settings.actions.setAudioInput(device);
},
setVideo: (device) => {
settings.actions.setVideoInput(device);
},
setEditSeal: () => {
let sealMode;
let sealEnabled = isEnabled();

View File

@ -9,7 +9,7 @@ export const SelectItemWrapper = styled.div`
height: 48px;
width: 100%;
padding-left: 8px;
padding-right: 8px;
padding-right: 16px;
display: flex;
align-items: center;

8
todo
View File

@ -5,3 +5,11 @@ calling:
- fullscreen
- device selection
add languages:
spanish
portugues
german
russian
trim docker image