import React from 'react'; import { useNavigate, useParams } from 'react-router-dom'; import PropTypes from 'prop-types'; import { useLingui } from '@lingui/react'; import { useTheme } from '@mui/material/styles'; import { Trans, t } from '@lingui/macro'; import makeStyles from '@mui/styles/makeStyles'; import useMediaQuery from '@mui/material/useMediaQuery'; import Backdrop from '@mui/material/Backdrop'; import Button from '@mui/material/Button'; import CircularProgress from '@mui/material/CircularProgress'; import Grid from '@mui/material/Grid'; import Tab from '@mui/material/Tab'; import Tabs from '@mui/material/Tabs'; import TextField from '@mui/material/TextField'; import ToggleButton from '@mui/material/ToggleButton'; import ToggleButtonGroup from '@mui/material/ToggleButtonGroup'; import Tooltip from '@mui/material/Tooltip'; import Typography from '@mui/material/Typography'; import * as helper from './helper'; import * as M from '../../utils/metadata'; import EncodingSelect from '../../misc/EncodingSelect'; import H from '../../utils/help'; import NotifyContext from '../../contexts/Notify'; import Paper from '../../misc/Paper'; import PaperHeader from '../../misc/PaperHeader'; import PaperFooter from '../../misc/PaperFooter'; import ProcessControl from '../../misc/controls/Process'; import SourceControl from '../../misc/controls/Source'; import Services from './Services'; import TabContent from './TabContent'; import TabPanel from '../../misc/TabPanel'; import TabsVerticalGrid from '../../misc/TabsVerticalGrid'; const useStyles = makeStyles((theme) => ({ buttonAbort: { marginBottom: '0.3em', }, gridContainer: { marginTop: '0.5em', }, buttonGroup: { marginTop: '0.5em', marginBottom: '-0.5em', }, })); export default function Add(props) { const theme = useTheme(); const breakpointUpSm = useMediaQuery(theme.breakpoints.up('sm')); const classes = useStyles(); const { i18n } = useLingui(); const navigate = useNavigate(); const { channelid: _channelid } = useParams(); const notify = React.useContext(NotifyContext); const [$service, setService] = React.useState(''); const [$settings, setSettings] = React.useState(M.initEgressMetadata({})); const [$sources, setSources] = React.useState([]); const [$localSources, setLocalSources] = React.useState([]); const [$filter, setFilter] = React.useState('all'); const [$tab, setTab] = React.useState('general'); const [$skills, setSkills] = React.useState(null); const [$metadata, setMetadata] = React.useState({ name: '', description: '', license: '', }); const [$saving, setSaving] = React.useState(false); React.useEffect(() => { (async () => { await load(); })(); // eslint-disable-next-line react-hooks/exhaustive-deps }, []); const load = async () => { const channelid = props.restreamer.SelectChannel(_channelid); if (channelid === '' || channelid !== _channelid) { navigate('/', { replace: true }); return; } const skills = await props.restreamer.Skills(); setSkills(skills); let ingest = await props.restreamer.GetIngestMetadata(_channelid); setMetadata({ ...$metadata, name: ingest.meta.name, description: ingest.meta.description, license: ingest.license, }); const localSources = []; localSources.push('hls+' + ingest.control.hls.storage); if (ingest.control.rtmp.enable) { localSources.push('rtmp'); } if (ingest.control.srt.enable) { localSources.push('srt'); } setLocalSources(localSources); setSources(helper.createSourcesFromStreams(ingest.streams)); }; const handleFilterChange = (event, value) => { if (!value) { return; } setFilter(value); }; const handleServiceSelect = (service) => () => { if (service.length !== 0) { const s = Services.Get(service); if (s === null) { return; } const serviceSkills = helper.conflateServiceSkills(s.requires, $skills); const profiles = $settings.profiles; profiles[0].video = helper.preselectProfile(profiles[0].video, 'video', $sources[0].streams, serviceSkills.codecs.video, $skills.encoders.video); profiles[0].audio = helper.preselectProfile(profiles[0].audio, 'audio', $sources[0].streams, serviceSkills.codecs.audio, $skills.encoders.audio); setSettings({ ...$settings, name: s.name, profiles: profiles, streams: M.createOutputStreams($sources, profiles), }); setTab('general'); } else { // Reset the service outputs and settings setSettings({ ...$settings, ...M.initEgressMetadata({}), }); } setService(service); }; const handleServiceChange = (outputs, settings) => { if (!Array.isArray(outputs)) { outputs = [outputs]; } setSettings({ ...$settings, outputs: outputs, settings: settings, }); }; const handleProcessing = (type) => (encoder, decoder) => { const profiles = $settings.profiles; profiles[0][type].encoder = encoder; profiles[0][type].decoder = decoder; setSettings({ ...$settings, profiles: profiles, streams: M.createOutputStreams($sources, profiles), }); }; const handleServiceDone = async () => { setSaving(true); const [global, inputs, outputs] = helper.createInputsOutputs($sources, $settings.profiles, $settings.outputs); const [id, err] = await props.restreamer.CreateEgress(_channelid, $service, global, inputs, outputs, $settings.control); if (err !== null) { setSaving(false); notify.Dispatch('error', 'save:egress:' + $service, i18n._(t`Failed to create publication service (${err.message})`)); return; } await props.restreamer.SetEgressMetadata(_channelid, id, $settings); let message = i18n._(t`The publication service has been created`); if ($settings.name.length !== 0) { message = i18n._(t`The publication service "${$settings.name}" has been created`); } setSaving(false); notify.Dispatch('success', 'save:egress:' + $service, message); navigate(`/${_channelid}/`); }; const handleServiceName = (event) => { const name = event.target.value; setSettings({ ...$settings, name: name, }); }; const handleControlChange = (what) => (control) => { setSettings({ ...$settings, control: { ...$settings.control, [what]: control, }, }); }; const handleAbort = () => { navigate(`/${_channelid}`); }; const handleChangeTab = (event, value) => { setTab(value); }; const handleHelp = () => { let topic = 'publication-add'; if ($service !== '') { topic = 'publication-' + $tab; } H(topic); }; const channelid = props.restreamer.SelectChannel(_channelid); if (channelid === '' || channelid !== _channelid) { navigate('/', { replace: true }); return null; } let serviceList = []; let ServiceControl = null; let serviceSkills = null; let service = {}; if ($service === '') { for (let s of Services.List()) { if ($filter !== 'all') { if (s.category !== $filter) { continue; } } const Icon = s.icon; // TODO: Style Tooltip + Fix Tooltip + Disabled if (helper.checkServiceRequirements(s.requires, $skills) === false) { serviceList.push( Incompatible Check the requirements } placement="left" arrow >
); } else { serviceList.push( ); } } } else { service = Services.Get($service); if (service === null) { return null; } ServiceControl = service.component; serviceSkills = helper.conflateServiceSkills(service.requires, $skills); } return ( {$service === '' && Add Publication} {$service !== '' && ( Add: {service.name} )} } onAbort={handleAbort} onHelp={handleHelp} /> {$service === '' ? ( All Platforms Software Protocols {serviceList} ) : ( General} value="general" /> Process control} value="process" /> Source} value="source" /> Encoding} value="encoding" /> {service.description} Service name} value={$settings.name} onChange={handleServiceName} /> Process Source Encoding Please use "Passthrough (copy)" if possible. Encoding requires additional CPU/GPU resources. Video Audio } buttonsRight={ } /> )} ); } Add.defaultProps = { restreamer: null, }; Add.propTypes = { restreamer: PropTypes.object.isRequired, };