Add to allow stream hints in case probing fails

This commit is contained in:
Ingo Oppermann 2024-04-26 21:41:09 +02:00
parent 9277f04b4b
commit 486d64ff19
No known key found for this signature in database
GPG Key ID: 2AB32426E9DD229E
2 changed files with 343 additions and 17 deletions

200
src/misc/modals/Hint.js Normal file
View File

@ -0,0 +1,200 @@
import React from 'react';
import { useLingui } from '@lingui/react';
import { Trans, t } from '@lingui/macro';
import Button from '@mui/material/Button';
import Grid from '@mui/material/Grid';
import Stack from '@mui/material/Stack';
import MenuItem from '@mui/material/MenuItem';
import Typography from '@mui/material/Typography';
import Dialog from './Dialog';
import Select from '../Select';
import Video from '../coders/settings/Video';
import Audio from '../coders/settings/Audio';
const Stream = function (props) {
const { i18n } = useLingui();
const handleChange = (what) => (event) => {
const value = event.target.value;
let stream = {
...props.stream,
};
if (what === 'type') {
if (value === 'audio') {
stream.codec = 'aac';
if (stream.sampling_hz === 0) {
stream.sampling_hz = 44100;
}
if (stream.layout === '') {
stream.layout = 'stereo';
stream.channels = 2;
}
} else {
stream.codec = 'h264';
if (stream.width === 0) {
stream.width = 1920;
stream.height = 1080;
}
}
stream.type = value;
} else if (what === 'size') {
const [width, height] = value.split('x');
stream.width = width;
stream.height = height;
} else {
stream[what] = value;
}
props.onChange(stream);
};
return (
<Grid container spacing={1}>
<Grid item xs={6}>
<Select label={<Trans>Type</Trans>} value={props.stream.type} onChange={handleChange('type')} disabled={props.locked}>
<MenuItem value="audio">Audio</MenuItem>
<MenuItem value="video">Video</MenuItem>
</Select>
</Grid>
{props.stream.type === 'audio' ? (
<React.Fragment>
<Grid item xs={6}>
<Select label={<Trans>Codec</Trans>} value={props.stream.codec} onChange={handleChange('codec')}>
<MenuItem value="aac">AAC</MenuItem>
<MenuItem value="mp3">MP3</MenuItem>
</Select>
</Grid>
<Grid item xs={12}>
<Audio.Sampling value={props.stream.sampling_hz} onChange={handleChange('sampling_hz')} allowCustom />
</Grid>
<Grid item xs={12}>
<Audio.Layout value={props.stream.layout} onChange={handleChange('layout')} allowCustom />
</Grid>
</React.Fragment>
) : (
<React.Fragment>
<Grid item xs={6}>
<Select label={<Trans>Codec</Trans>} value={props.stream.codec} onChange={handleChange('codec')}>
<MenuItem value="h264">H264</MenuItem>
<MenuItem value="hevc">HEVC</MenuItem>
<MenuItem value="vp9">VP9</MenuItem>
<MenuItem value="av1">AV1</MenuItem>
<MenuItem value="vp8">VP8</MenuItem>
</Select>
</Grid>
<Grid item xs={12}>
<Video.Size value={props.stream.width + 'x' + props.stream.height} onChange={handleChange('size')} allowCustom />
</Grid>
</React.Fragment>
)}
</Grid>
);
};
Stream.defaultProps = {
stream: {},
locked: false,
onChange: () => {},
};
const Streams = function (props) {
const handleChange = (index) => (stream) => {
const streams = props.streams.slice();
streams[index] = stream;
props.onChange(streams);
};
const handleAddStream = () => {
const streams = props.streams.slice();
streams.push({
index: props.type === 'video' ? 0 : 1,
stream: streams.length,
type: 'audio',
codec: 'aac',
width: 0,
height: 0,
sampling_hz: 44100,
layout: 'stereo',
channels: 2,
});
props.onChange(streams);
};
const handleRemoveStream = (index) => () => {
const streams = props.streams.toSpliced(index, 1);
props.onChange(streams);
};
return (
<Grid container spacing={1}>
{props.streams.map((stream, index) => (
<Grid key={stream.index + ':' + stream.stream} item xs={12}>
<Stack spacing={1}>
<Typography>Stream {stream.stream}</Typography>
<Stream stream={stream} onChange={handleChange(index)} locked={index === 0} />
{index > 0 && (
<Button variant="outlined" color="secondary" onClick={handleRemoveStream(index)}>
<Trans>Remove Stream</Trans>
</Button>
)}
</Stack>
</Grid>
))}
<Grid item xs={12}>
<Button variant="outlined" color="default" onClick={handleAddStream}>
<Trans>Add Stream</Trans>
</Button>
</Grid>
</Grid>
);
};
Streams.defaultProps = {
streams: [],
type: '',
onChange: () => {},
};
const Component = function (props) {
return (
<Dialog
open={props.open}
onClose={props.onClose}
title={props.title}
buttonsLeft={
<Button variant="outlined" color="secondary" onClick={props.onClose}>
<Trans>Close</Trans>
</Button>
}
buttonsRight={
<Button variant="outlined" color="default" onClick={props.onDone}>
<Trans>Save</Trans>
</Button>
}
>
<Streams type={props.type} streams={props.streams} onChange={props.onChange} />
</Dialog>
);
};
export default Component;
Component.defaultProps = {
open: false,
title: '',
streams: [],
type: '',
onClose: null,
onDone: () => {},
onHelp: null,
};

View File

@ -15,6 +15,7 @@ import BoxText from '../../misc/BoxText';
import EncodingSelect from '../../misc/EncodingSelect'; import EncodingSelect from '../../misc/EncodingSelect';
import PaperFooter from '../../misc/PaperFooter'; import PaperFooter from '../../misc/PaperFooter';
import ProbeModal from '../../misc/modals/Probe'; import ProbeModal from '../../misc/modals/Probe';
import HintModal from '../../misc/modals/Hint';
import SourceSelect from './SourceSelect'; import SourceSelect from './SourceSelect';
import StreamSelect from './StreamSelect'; import StreamSelect from './StreamSelect';
@ -39,10 +40,15 @@ export default function Profile(props) {
status: 'none', status: 'none',
}); });
const [$skillsRefresh, setSkillsRefresh] = React.useState(false); const [$skillsRefresh, setSkillsRefresh] = React.useState(false);
const [$modal, setModal] = React.useState({ const [$probeModal, setProbeModal] = React.useState({
open: false, open: false,
data: '', data: '',
}); });
const [$hintModal, setHintModal] = React.useState({
open: false,
type: '',
streams: [],
});
const [$activeStep, setActiveStep] = React.useState(props.startWith === 'audio' ? 1 : 0); const [$activeStep, setActiveStep] = React.useState(props.startWith === 'audio' ? 1 : 0);
const [$ready, setReady] = React.useState(false); const [$ready, setReady] = React.useState(false);
@ -106,6 +112,12 @@ export default function Profile(props) {
const res = await props.onProbe(inputs); const res = await props.onProbe(inputs);
const status = handleProbeStreams(type, device, settings, inputs, res);
return status === 'success';
};
const handleProbeStreams = (type, device, settings, inputs, res) => {
let status = M.analyzeStreams(type, res.streams); let status = M.analyzeStreams(type, res.streams);
if (type === 'video') { if (type === 'video') {
@ -194,8 +206,6 @@ export default function Profile(props) {
}, },
}); });
} }
return status === 'success';
}; };
const handleRefresh = async () => { const handleRefresh = async () => {
@ -242,24 +252,24 @@ export default function Profile(props) {
props.onAbort(); props.onAbort();
}; };
const handleModal = (type) => (event) => { const handleProbeLogModal = (type) => (event) => {
event.preventDefault(); event.preventDefault();
if (type === 'video') { if (type === 'video') {
setModal({ setProbeModal({
...$modal, ...$probeModal,
open: true, open: true,
data: $videoProbe.log.join('\n'), data: $videoProbe.log.join('\n'),
}); });
} else if (type === 'audio') { } else if (type === 'audio') {
setModal({ setProbeModal({
...$modal, ...$probeModal,
open: true, open: true,
data: $audioProbe.log.join('\n'), data: $audioProbe.log.join('\n'),
}); });
} else { } else {
setModal({ setProbeModal({
...$modal, ...$probeModal,
open: false, open: false,
data: '', data: '',
}); });
@ -348,6 +358,104 @@ export default function Profile(props) {
}); });
}; };
const handleHintModal = (type, streams) => (event) => {
if (event) {
event.preventDefault();
}
if (!streams) {
streams = [];
}
if (streams.length > 0) {
return streams;
}
if (type === 'video') {
streams = [
{
index: 0,
stream: 0,
type: 'video',
codec: 'h264',
width: 1920,
height: 1080,
sampling_hz: 0,
layout: '',
channels: 0,
},
];
} else if (type === 'audio') {
streams = [
{
index: 1,
stream: 0,
type: 'audio',
codec: 'aac',
width: 0,
height: 0,
sampling_hz: 44100,
layout: 'stereo',
channels: 2,
},
];
}
if (type === 'video') {
setHintModal({
...$hintModal,
open: true,
type: type,
streams: streams,
});
} else if (type === 'audio') {
setHintModal({
...$hintModal,
open: true,
type: type,
streams: streams,
});
} else {
setHintModal({
...$hintModal,
open: false,
type: '',
streams: [],
});
}
};
const handleHintChange = (streams) => {
setHintModal({
...$hintModal,
streams: streams,
});
};
const handleHintCancel = () => {
setHintModal({
streams: [],
});
handleHintModal('none')(null);
};
const handleHintDone = () => {
const type = $hintModal.type;
const device = $sources[type].type;
const settings = $sources[type].settings;
const inputs = $sources[type].inputs;
const probe = {
streams: $hintModal.streams,
log: ['Stream hints'],
};
handleProbeStreams(type, device, settings, inputs, probe);
handleHintModal('none')(null);
};
if ($ready === false) { if ($ready === false) {
return null; return null;
} }
@ -383,12 +491,21 @@ export default function Profile(props) {
<Typography> <Typography>
<Trans> <Trans>
Failed to probe the source. Please check the{' '} Failed to probe the source. Please check the{' '}
<Link color="textSecondary" href="#!" onClick={handleModal('video')}> <Link color="textSecondary" href="#!" onClick={handleProbeLogModal('video')}>
probe details probe details
</Link> </Link>
. .
</Trans> </Trans>
</Typography> </Typography>
<Typography>
<Trans>
In order to proceed anyways, you can provide{' '}
<Link color="textSecondary" href="#!" onClick={handleHintModal('video', [])}>
hints
</Link>{' '}
about the available streams.
</Trans>
</Typography>
</BoxText> </BoxText>
</Grid> </Grid>
)} )}
@ -399,7 +516,7 @@ export default function Profile(props) {
<Typography> <Typography>
<Trans> <Trans>
The source doesn't provide any video streams. Please check the{' '} The source doesn't provide any video streams. Please check the{' '}
<Link href="#!" onClick={handleModal('video')}> <Link href="#!" onClick={handleProbeLogModal('video')}>
probe details probe details
</Link> </Link>
. .
@ -421,7 +538,7 @@ export default function Profile(props) {
<Grid item xs={12} align="right"> <Grid item xs={12} align="right">
<Typography> <Typography>
<Trans> <Trans>
<Link href="#!" onClick={handleModal('video')}> <Link href="#!" onClick={handleProbeLogModal('video')}>
Show probe details Show probe details
</Link> </Link>
</Trans> </Trans>
@ -533,7 +650,7 @@ export default function Profile(props) {
<Typography> <Typography>
<Trans> <Trans>
Failed to probe the source. Please check the{' '} Failed to probe the source. Please check the{' '}
<Link href="#!" onClick={handleModal('audio')}> <Link href="#!" onClick={handleProbeLogModal('audio')}>
probe details probe details
</Link> </Link>
. .
@ -549,7 +666,7 @@ export default function Profile(props) {
<Typography> <Typography>
<Trans> <Trans>
The source doesn't provide any audio streams. Please check the{' '} The source doesn't provide any audio streams. Please check the{' '}
<Link href="#!" onClick={handleModal('audio')}> <Link href="#!" onClick={handleProbeLogModal('audio')}>
probe details probe details
</Link> </Link>
. .
@ -571,7 +688,7 @@ export default function Profile(props) {
<Grid item xs={12} align="right"> <Grid item xs={12} align="right">
<Typography> <Typography>
<Trans> <Trans>
<Link href="#!" onClick={handleModal('audio')}> <Link href="#!" onClick={handleProbeLogModal('audio')}>
Show probe details Show probe details
</Link> </Link>
</Trans> </Trans>
@ -629,7 +746,16 @@ export default function Profile(props) {
<Backdrop open={$videoProbe.probing || $audioProbe.probing || $skillsRefresh}> <Backdrop open={$videoProbe.probing || $audioProbe.probing || $skillsRefresh}>
<CircularProgress color="inherit" /> <CircularProgress color="inherit" />
</Backdrop> </Backdrop>
<ProbeModal open={$modal.open} onClose={handleModal('none')} data={$modal.data} /> <ProbeModal open={$probeModal.open} onClose={handleProbeLogModal('none')} data={$probeModal.data} />
<HintModal
open={$hintModal.open}
onClose={handleHintCancel}
onChange={handleHintChange}
onDone={handleHintDone}
title="Stream hints"
type={$hintModal.type}
streams={$hintModal.streams}
/>
</React.Fragment> </React.Fragment>
); );
} }