diff --git a/src/views/Edit/Profile.js b/src/views/Edit/Profile.js
index 2907c44..b7b3e57 100644
--- a/src/views/Edit/Profile.js
+++ b/src/views/Edit/Profile.js
@@ -80,6 +80,7 @@ export default function Profile({
const load = async () => {
// Add pseudo sources
skills.sources.noaudio = [];
+ skills.sources.sdp = [];
let audio = $sources.audio;
@@ -145,6 +146,7 @@ export default function Profile({
// Add pseudo sources
skills.sources.noaudio = [];
+ skills.sources.sdp = [];
let hasAudio = false;
for (let i = 0; i < res.streams.length; i++) {
diff --git a/src/views/Edit/Sources/SDP.js b/src/views/Edit/Sources/SDP.js
new file mode 100644
index 0000000..e0934e4
--- /dev/null
+++ b/src/views/Edit/Sources/SDP.js
@@ -0,0 +1,208 @@
+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: 'application/sdp', extension: '*.sdp', maxSize: 2 * 1024 * 1024 },
+ { mimetype: '', extension: '*.sdp', maxSize: 2 * 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('-protocol_whitelist', 'file,udp,rtp');
+ input.options.push('-buffer_size', '671088640');
+ input.options.push('-thread_queue_size', '4096');
+
+ 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('input.sdp', 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 image or video 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 = 'sdp';
+const name = SDP;
+const capabilities = ['video', 'audio'];
+const ffversion = '^4.1.0 || ^5.0.0 || ^6.1.0';
+
+const func = {
+ initSettings,
+ createInputs,
+};
+
+export { id, name, capabilities, ffversion, SourceIcon as icon, Source as component, func };
diff --git a/src/views/Edit/Sources/index.js b/src/views/Edit/Sources/index.js
index 65479bd..8b64adf 100644
--- a/src/views/Edit/Sources/index.js
+++ b/src/views/Edit/Sources/index.js
@@ -9,6 +9,7 @@ import * as VideoLoop from './VideoLoop';
import * as AudioLoop from './AudioLoop';
import * as VirtualAudio from './VirtualAudio';
import * as VirtualVideo from './VirtualVideo';
+import * as SDP from './SDP';
class Registry {
constructor() {
@@ -50,5 +51,6 @@ registry.Register(NoAudio);
registry.Register(VideoAudio);
registry.Register(VideoLoop);
registry.Register(AudioLoop);
+registry.Register(SDP);
export default registry;