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
+
+
+
+
+
+
+
+
+ );
+}
+
+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;