diff --git a/src/utils/restreamer.js b/src/utils/restreamer.js index 1227ca4..3b6e146 100644 --- a/src/utils/restreamer.js +++ b/src/utils/restreamer.js @@ -508,6 +508,7 @@ class Restreamer { virtualaudio: [], virtualvideo: [], videoloop: [], + audioloop: [], }, sinks: {}, }; diff --git a/src/views/Edit/Sources/AudioLoop.js b/src/views/Edit/Sources/AudioLoop.js new file mode 100644 index 0000000..cd195d6 --- /dev/null +++ b/src/views/Edit/Sources/AudioLoop.js @@ -0,0 +1,203 @@ +import React from 'react'; + +import { Trans } 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 Icon from '@mui/icons-material/Cached'; +import TextField from '@mui/material/TextField'; +import Typography from '@mui/material/Typography'; + +import Dialog from '../../../misc/modals/Dialog'; +import Filesize from '../../../misc/Filesize'; +import FormInlineButton from '../../../misc/FormInlineButton'; +import UploadButton from '../../../misc/UploadButton'; + +const imageTypes = [{ mimetype: 'audio/*', extension: 'audio', maxSize: 25 * 1024 * 1024 }]; + +const useStyles = makeStyles((theme) => ({ + gridContainer: { + marginTop: '0.5em', + }, +})); + +const initSettings = (initialSettings) => { + if (!initialSettings) { + initialSettings = {}; + } + + const settings = { + address: '', + mimetype: '', + ...initialSettings, + }; + + return settings; +}; + +const createInputs = (settings) => { + const address = '{diskfs}' + settings.address; + const input = { + address: address, + options: [], + }; + + input.options.push('-stream_loop', '-1'); + input.options.push('-re'); + + return [input]; +}; + +function Source(props) { + const classes = useStyles(); + const settings = initSettings(props.settings); + const [$saving, setSaving] = React.useState(false); + const [$error, setError] = React.useState({ + open: false, + title: '', + message: '', + }); + + const handleFileUpload = async (data, extension, mimetype) => { + const path = await props.onStore('audioloop.source', data); + + props.onChange({ + ...settings, + address: path, + mimetype: mimetype, + }); + + 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.join(', ')} + + ); + 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 handleProbe = () => { + props.onProbe(settings, createInputs(settings)); + }; + + return ( + + + + + Upload an audio file ({imageTypes.map((t) => t.mimetype).join(', ')}) in order to loop it. + + + + File path} value={settings.address} readOnly /> + + + Upload} + acceptTypes={imageTypes} + onStart={handleUploadStart} + onError={handleUploadError(Uploading the file failed)} + onUpload={handleFileUpload} + /> + + + + Probe + + + + + + + + OK + + } + > + {$error.message} + + + ); +} + +Source.defaultProps = { + knownDevices: [], + settings: {}, + onChange: function (settings) {}, + onProbe: function (settings, inputs) {}, + onRefresh: function () {}, + onStore: function (name, data) { + return ''; + }, +}; + +function SourceIcon(props) { + return ; +} + +const id = 'audioloop'; +const name = Loop; +const capabilities = ['audio']; +const ffversion = '^4.1.0 || ^5.0.0'; + +const func = { + initSettings, + createInputs, +}; + +export { id, name, capabilities, ffversion, SourceIcon as icon, Source as component, func }; diff --git a/src/views/Edit/Sources/VideoLoop.js b/src/views/Edit/Sources/VideoLoop.js index 298768a..f532547 100644 --- a/src/views/Edit/Sources/VideoLoop.js +++ b/src/views/Edit/Sources/VideoLoop.js @@ -69,7 +69,7 @@ function Source(props) { }); const handleFileUpload = async (data, extension, mimetype) => { - const path = await props.onStore('loop.source', data); + const path = await props.onStore('videoloop.source', data); props.onChange({ ...settings, diff --git a/src/views/Edit/Sources/index.js b/src/views/Edit/Sources/index.js index 30f4aae..65479bd 100644 --- a/src/views/Edit/Sources/index.js +++ b/src/views/Edit/Sources/index.js @@ -6,6 +6,7 @@ import * as Raspicam from './Raspicam'; import * as Video4Linux from './V4L'; import * as VideoAudio from './VideoAudio'; import * as VideoLoop from './VideoLoop'; +import * as AudioLoop from './AudioLoop'; import * as VirtualAudio from './VirtualAudio'; import * as VirtualVideo from './VirtualVideo'; @@ -48,5 +49,6 @@ registry.Register(VirtualVideo); registry.Register(NoAudio); registry.Register(VideoAudio); registry.Register(VideoLoop); +registry.Register(AudioLoop); export default registry;