import React from 'react';
import { useNavigate, useParams } from 'react-router-dom';
import { useLingui } from '@lingui/react';
import { Trans, t } from '@lingui/macro';
import makeStyles from '@mui/styles/makeStyles';
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 MenuItem from '@mui/material/MenuItem';
import Tab from '@mui/material/Tab';
import TextField from '@mui/material/TextField';
import Typography from '@mui/material/Typography';
import Checkbox from '../../misc/Checkbox';
import Dialog from '../../misc/modals/Dialog';
import Filesize from '../../misc/Filesize';
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 Player from '../../misc/Player';
import Select from '../../misc/Select';
import UploadButton from '../../misc/UploadButton';
import TabPanel from '../../misc/TabPanel';
import TabsHorizontal from '../../misc/TabsHorizontal';
import TextFieldCopy from '../../misc/TextFieldCopy';
const useStyles = makeStyles((theme) => ({
gridContainer: {
paddingTop: '1em',
},
playerL1: {
padding: '4px 1px 4px 9px',
},
playerL2: {
position: 'relative',
width: '100%',
paddingTop: '56.25%',
},
playerL3: {
position: 'absolute',
top: 0,
left: 0,
bottom: 0,
right: 0,
backgroundColor: theme.palette.common.black,
},
}));
const logoImageTypes = [
{ mimetype: 'image/gif', extension: 'gif', maxSize: 1 * 1024 * 1024 },
{ mimetype: 'image/png', extension: 'png', maxSize: 1 * 1024 * 1024 },
{ mimetype: 'image/jpeg', extension: 'jpg', maxSize: 1 * 1024 * 1024 },
{ mimetype: 'image/svg+xml', extension: 'svg', maxSize: 1 * 1024 * 1024 },
];
const posterImageTypes = [
{ mimetype: 'image/gif', extension: 'gif', maxSize: 1 * 1024 * 1024 },
{ mimetype: 'image/png', extension: 'png', maxSize: 1 * 1024 * 1024 },
{ mimetype: 'image/jpeg', extension: 'jpg', maxSize: 1 * 1024 * 1024 },
];
export default function Edit(props) {
const classes = useStyles();
const navigate = useNavigate();
const { channelid: _channelid } = useParams();
const { i18n } = useLingui();
const address = props.restreamer.Address();
const timeout = React.useRef();
const notify = React.useContext(NotifyContext);
const [$player] = React.useState('videojs-public');
const [$ready, setReady] = React.useState(false);
const [$state, setState] = React.useState('disconnected');
const [$metadata, setMetadata] = React.useState({});
const [$settings, setSettings] = React.useState({});
const [$tab, setTab] = React.useState('embed');
const [$revision, setRevision] = React.useState(0);
const [$saving, setSaving] = React.useState(false);
const [$error, setError] = React.useState({
open: false,
title: '',
message: '',
});
const [$invalid, setInvalid] = React.useState('');
React.useEffect(() => {
(async () => {
await mount();
})();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
React.useEffect(() => {
if ($invalid.length !== 0) {
navigate($invalid, { replace: true });
}
}, [navigate, $invalid]);
const mount = async () => {
const channelid = props.restreamer.SelectChannel(_channelid);
if (channelid === '' || channelid !== _channelid) {
setInvalid('/');
return;
}
const proc = await props.restreamer.GetIngest(channelid, ['state', 'metadata']);
if (proc === null) {
notify.Dispatch('warning', 'notfound:ingest', i18n._(t`Main channel not found`));
setInvalid(`/${_channelid}/`);
return;
}
setMetadata(proc.metadata);
setState(proc.progress.state);
setSettings(props.restreamer.InitPlayerSettings(proc.metadata.player));
setReady(true);
};
const handleChange =
(what, section = '') =>
(event) => {
const value = event.target.value;
const settings = $settings;
if (section === '') {
if (['autoplay', 'mute', 'statistics', 'chromecast', 'airplay'].includes(what)) {
settings[what] = !settings[what];
} else {
settings[what] = value;
}
} else if (section === 'color') {
settings.color[what] = value;
} else if (section === 'ga') {
settings.ga[what] = value;
} else if (section === 'logo') {
settings.logo[what] = value;
}
if (timeout.current !== null) {
clearTimeout(timeout.current);
timeout.current = null;
}
timeout.current = setTimeout(() => {
timeout.current = null;
setRevision($revision + 1);
}, 500);
setSettings({
...$settings,
...settings,
});
};
const handleLogoUpload = async (data, extension) => {
const path = await props.restreamer.UploadLogo(_channelid, data, extension);
handleChange(
'image',
'logo',
)({
target: {
value: path,
},
});
setSaving(false);
};
const handlePosterUpload = async (data, extension) => {
const path = await props.restreamer.UploadPoster(_channelid, data, extension);
handleChange('poster')({
target: {
value: path,
},
});
setSaving(false);
};
const handleUploadStart = () => {
setSaving(true);
};
const handleUploadError = (title) => (err) => {
let message = null;
switch (err.type) {
case 'nofiles':
message = Please select a file to upload.;
break;
case 'mimetype':
message = (
The selected file type ({err.actual}) is not allowed. Allowed file types are {err.allowed}
);
break;
case 'size':
message = (
The selected file is too big (
). Only are allowed.
);
break;
case 'read':
message = There was an error during upload: {err.message};
break;
default:
message = Unknown upload error;
}
setSaving(false);
showUploadError(title, message);
};
const showUploadError = (title, message) => {
setError({
...$error,
open: true,
title: title,
message: message,
});
};
const hideUploadError = () => {
setError({
...$error,
open: false,
});
};
const handleLogoReset = (event) => {
// For the cleanup of the core, we need to check the following:
// 1. is the image on the core or external?
// 2. is the image used somewhere else?
// 3. OK via dialog
handleChange(
'image',
'logo',
)({
target: {
value: '',
},
});
handleChange(
'position',
'logo',
)({
target: {
value: 'top-left',
},
});
handleChange(
'link',
'logo',
)({
target: {
value: '',
},
});
};
const handlePosterReset = (event) => {
// For the cleanup of the core, we need to check the following:
// 1. is the image on the core or external?
// 2. is the image used somewhere else?
// 3. OK via dialog
handleChange('poster')({
target: {
value: '',
},
});
};
const handleDone = async () => {
setSaving(true);
const metadata = {
...$metadata,
player: $settings,
};
await props.restreamer.SetIngestMetadata(_channelid, metadata);
await props.restreamer.UpdatePlayer(_channelid);
setSaving(false);
notify.Dispatch('success', 'save:player', i18n._(t`Player settings saved`));
};
const handleChangeTab = (event, value) => {
setTab(value);
};
const handleAbort = () => {
navigate(`/${_channelid}/`);
};
const handleHelp = () => {
H('player-' + $tab);
};
const prepareUrl = (url) => {
if (url.length === 0) {
return '';
}
if (url.match(/^https?:\/\//) === null) {
url = address + url;
}
try {
let u = new URL(url);
u.searchParams.set('_rscache', Math.random());
return u.href;
} catch (e) {
return url + '?' + Math.random();
}
};
if ($ready === false) {
return null;
}
const storage = $metadata.control.hls.storage;
const manifest = props.restreamer.GetChannelAddress('hls+' + storage, _channelid);
const poster = $settings.poster ? prepareUrl($settings.poster) : props.restreamer.GetChannelAddress('snapshot+' + storage, _channelid);
const playerAddress = props.restreamer.GetPublicAddress('player', _channelid);
const iframeCode = props.restreamer.GetPublicIframeCode(_channelid);
const logo = { ...$settings.logo, image: prepareUrl($settings.logo.image) };
return (
EDIT: Player} onAbort={handleAbort} onHelp={handleHelp} />
{$state !== 'connected' ? (
No video
) : (
)}
Embed} value="embed" />
Logo} value="logo" />
Poster} value="poster" />
Playback} value="playback" />
Player URL} value={playerAddress} />
iframe code} value={iframeCode} />
Seekbar color}
value={$settings.color.seekbar}
onChange={handleChange('seekbar', 'color')}
/>
Button color}
value={$settings.color.buttons}
onChange={handleChange('buttons', 'color')}
/>
Image URL}
value={$settings.logo.image}
onChange={handleChange('image', 'logo')}
/>
Upload}
acceptTypes={logoImageTypes}
onStart={handleUploadStart}
onError={handleUploadError(Uploading the logo failed)}
onUpload={handleLogoUpload}
/>
Link}
value={$settings.logo.link}
onChange={handleChange('link', 'logo')}
/>
Poster image URL}
value={$settings.poster}
onChange={handleChange('poster')}
/>
Upload}
acceptTypes={posterImageTypes}
onStart={handleUploadStart}
onError={handleUploadError(Uploading the poster failed)}
onUpload={handlePosterUpload}
/>
Google Analytics ID}
value={$settings.gaAccount}
onChange={handleChange('account', 'ga')}
/>
Google Analytics Tracker Name}
value={$settings.gaName}
onChange={handleChange('name', 'ga')}
/>
Enable nerd statistics}
checked={$settings.statistics}
onChange={handleChange('statistics')}
/>
Autoplay} checked={$settings.autoplay} onChange={handleChange('autoplay')} />
Mute} checked={$settings.mute} onChange={handleChange('mute')} />
Chromecast} checked={$settings.chromecast} onChange={handleChange('chromecast')} />
AirPlay} checked={$settings.airplay} onChange={handleChange('airplay')} />
Close
}
buttonsRight={
{$settings.logo.image && $tab === 'logo' && (
)}
{$settings.poster && $tab === 'poster' && (
)}
}
/>
);
}
Edit.defaultProps = {
restreamer: null,
};