diff --git a/src/utils/restreamer.js b/src/utils/restreamer.js
index 75488aa..e337de1 100644
--- a/src/utils/restreamer.js
+++ b/src/utils/restreamer.js
@@ -837,6 +837,8 @@ class Restreamer {
config.source.network.srt.passphrase = val.config.srt.passphrase;
config.source.network.srt.token = val.config.srt.token;
+ config.source.network.srt.host = config.hostname;
+
let [srt_host, srt_port] = splitHostPort(val.config.srt.address);
config.source.network.srt.local = srt_host.length !== 0 ? srt_host : 'localhost';
config.source.network.srt.host += ':' + srt_port;
@@ -862,6 +864,7 @@ class Restreamer {
config.source.network.rtmp.name = this.channel.channelid;
config.source.network.hls.name = this.channel.channelid;
+ config.source.network.srt.name = this.channel.channelid;
return config;
}
diff --git a/src/views/Edit/Sources/Network.js b/src/views/Edit/Sources/Network.js
index 2ceb2af..3aa0855 100644
--- a/src/views/Edit/Sources/Network.js
+++ b/src/views/Edit/Sources/Network.js
@@ -16,8 +16,10 @@ import Icon from '@mui/icons-material/AccountTree';
import MenuItem from '@mui/material/MenuItem';
import TextField from '@mui/material/TextField';
import Typography from '@mui/material/Typography';
+import WarningIcon from '@mui/icons-material/Warning';
import BoxTextarea from '../../../misc/BoxTextarea';
+import BoxText from '../../../misc/BoxText';
import Checkbox from '../../../misc/Checkbox';
import FormInlineButton from '../../../misc/FormInlineButton';
import MultiSelect from '../../../misc/MultiSelect';
@@ -84,6 +86,7 @@ const initConfig = (initialConfig) => {
const config = {
rtmp: {},
+ srt: {},
hls: {},
...initialConfig,
};
@@ -99,6 +102,16 @@ const initConfig = (initialConfig) => {
...config.rtmp,
};
+ config.srt = {
+ enabled: false,
+ host: 'localhost',
+ local: 'localhost',
+ token: '',
+ passphrase: '',
+ name: '',
+ ...config.srt,
+ };
+
config.hls = {
secure: false,
host: 'localhost',
@@ -127,11 +140,20 @@ const initSkills = (initialSkills) => {
...skills.version,
};
+ skills.formats = {
+ demuxers: [],
+ ...skills.formats,
+ };
+
skills.protocols = {
input: [],
...skills.protocols,
};
+ if (skills.formats.demuxers.includes('rtsp')) {
+ skills.protocols.input.push('rtsp');
+ }
+
return skills;
};
@@ -158,8 +180,12 @@ const createInputs = (settings, config, skills) => {
if (settings.mode === 'push') {
if (settings.push.type === 'hls') {
input.address = getLocalHLS(config);
- } else {
+ } else if (settings.push.type === 'rtmp') {
input.address = getLocalRTMP(config);
+ } else if (settings.push.type === 'srt') {
+ input.address = getLocalSRT(config);
+ } else {
+ input.address = '';
}
} else {
input.address = settings.address;
@@ -285,6 +311,26 @@ const isAuthProtocol = (url) => {
return false;
};
+const isSupportedProtocol = (url, supportedProtocols) => {
+ const protocol = getProtocol(url);
+ if (protocol.length === 0) {
+ return 0;
+ }
+
+ if (!supportedProtocols.includes(protocol)) {
+ return -1;
+ }
+
+ return 1;
+};
+
+const getHLSAddress = (host, credentials, name, secure) => {
+ // Test for IPv6 addresses and put brackets around
+ let url = 'http' + (secure ? 's' : '') + '://' + (credentials.length !== 0 ? credentials + '@' : '') + host + '/memfs/ingest/' + name + '.m3u8';
+
+ return url;
+};
+
const getRTMPAddress = (host, app, name, token, secure) => {
let url = 'rtmp' + (secure ? 's' : '') + '://' + host + app + '/' + name + '.stream';
@@ -295,21 +341,18 @@ const getRTMPAddress = (host, app, name, token, secure) => {
return url;
};
-const getRTMP = (config, name) => {
- const url = getRTMPAddress(config.rtmp.host, config.rtmp.app, config.rtmp.name, config.rtmp.token, config.rtmp.secure);
+const getSRTAddress = (host, name, token, passphrase) => {
+ let url =
+ 'srt' +
+ '://' +
+ host +
+ '?mode=caller&transtype=live&streamid=#!:m=publish,r=' +
+ name +
+ (token.length !== 0 ? ',token=' + encodeURIComponent(token) : '');
- return [url];
-};
-
-const getLocalRTMP = (config, name) => {
- let url = getRTMPAddress(config.rtmp.local, config.rtmp.app, config.rtmp.name, config.rtmp.token, config.rtmp.secure);
-
- return url;
-};
-
-const getHLSAddress = (host, credentials, name, secure) => {
- // Test for IPv6 addresses and put brackets around
- let url = 'http' + (secure ? 's' : '') + '://' + (credentials.length !== 0 ? credentials + '@' : '') + host + '/memfs/ingest/' + name + '.m3u8';
+ if (passphrase.length !== 0) {
+ url += '&passphrase=' + encodeURIComponent(passphrase);
+ }
return url;
};
@@ -320,12 +363,32 @@ const getHLS = (config, name) => {
return [url];
};
+const getRTMP = (config) => {
+ const url = getRTMPAddress(config.rtmp.host, config.rtmp.app, config.rtmp.name, config.rtmp.token, config.rtmp.secure);
+
+ return [url];
+};
+
+const getSRT = (config) => {
+ const url = getSRTAddress(config.srt.host, config.srt.name, config.srt.token, config.srt.passphrase);
+
+ return [url];
+};
+
const getLocalHLS = (config, name) => {
let url = getHLSAddress(config.hls.local, '', config.hls.name, false);
return url;
};
+const getLocalRTMP = (config) => {
+ return getRTMPAddress(config.rtmp.local, config.rtmp.app, config.rtmp.name, config.rtmp.token, config.rtmp.secure);
+};
+
+const getLocalSRT = (config) => {
+ return getSRTAddress(config.srt.local, config.srt.name, config.srt.token, config.srt.passphrase);
+};
+
const isValidURL = (address) => {
const protocol = getProtocolClass(address);
if (protocol.length === 0) {
@@ -340,6 +403,7 @@ function Pull(props) {
const settings = props.settings;
const protocolClass = getProtocolClass(settings.address);
const authProtocol = isAuthProtocol(settings.address);
+ const supportedProtocol = isSupportedProtocol(settings.address, props.skills.protocols.input);
return (
@@ -361,53 +425,132 @@ function Pull(props) {
Supports HTTP (HLS, DASH), RTP, RTSP, RTMP, SRT and more.
- {authProtocol && (
-
-
- Username}
- value={settings.username}
- onChange={props.onChange('', 'username')}
- />
-
- Username for the device.
-
-
-
- Password}
- value={settings.password}
- onChange={props.onChange('', 'password')}
- />
-
- Password for the device.
-
-
-
- )}
-
-
- }>
+ {supportedProtocol === -1 && (
+
+
+
- Advanced settings
+ This protocol is unknown or not supported by the available FFmpeg binary.
-
-
-
- {protocolClass === 'rtsp' && (
-
+
+
+ )}
+ {supportedProtocol === 1 && (
+
+ {authProtocol && (
+
+
+ Username}
+ value={settings.username}
+ onChange={props.onChange('', 'username')}
+ />
+
+ Username for the device.
+
+
+
+ Password}
+ value={settings.password}
+ onChange={props.onChange('', 'password')}
+ />
+
+ Password for the device.
+
+
+
+ )}
+
+
+ }>
+
+ Advanced settings
+
+
+
+
+ {protocolClass === 'rtsp' && (
+
+
+
+ RTSP
+
+
+
+ UDP transport}
+ checked={settings.rtsp.udp}
+ onChange={props.onChange('rtsp', 'udp')}
+ />
+
+
+ Socket timeout (microseconds)}
+ value={settings.rtsp.stimeout}
+ onChange={props.onChange('rtsp', 'stimeout')}
+ />
+
+
+ )}
+ {protocolClass === 'http' && (
+
+
+
+ HTTP and HTTPS
+
+
+
+ Read input at native speed}
+ checked={settings.http.readNative}
+ onChange={props.onChange('http', 'readNative')}
+ />
+ Force input framerate}
+ checked={settings.http.forceFramerate}
+ onChange={props.onChange('http', 'forceFramerate')}
+ />
+
+ {settings.http.forceFramerate === true && (
+
+ Framerate}
+ value={settings.http.framerate}
+ onChange={props.onChange('http', 'framerate')}
+ />
+
+ )}
+
+
+
+
+ )}
- RTSP
+ General
-
- UDP transport} checked={settings.rtsp.udp} onChange={props.onChange('rtsp', 'udp')} />
-
Socket timeout (microseconds)}
- value={settings.rtsp.stimeout}
- onChange={props.onChange('rtsp', 'stimeout')}
+ label="thread_queue_size"
+ value={settings.general.thread_queue_size}
+ onChange={props.onChange('general', 'thread_queue_size')}
/>
-
- )}
- {protocolClass === 'http' && (
-
-
-
- HTTP and HTTPS
-
-
- Read input at native speed}
- checked={settings.http.readNative}
- onChange={props.onChange('http', 'readNative')}
- />
- Force input framerate}
- checked={settings.http.forceFramerate}
- onChange={props.onChange('http', 'forceFramerate')}
- />
+
+
+
+
+
+
+
+
+
+
+
- {settings.http.forceFramerate === true && (
-
- Framerate}
- value={settings.http.framerate}
- onChange={props.onChange('http', 'framerate')}
- />
-
- )}
-
-
-
-
- )}
-
-
- General
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+ )}
-
+
Probe
@@ -513,27 +595,51 @@ function Push(props) {
const classes = useStyles();
const settings = props.settings;
+ //const supportsHLS = isSupportedProtocol('http://', props.skills.protocols.input);
+ const supportsRTMP = isSupportedProtocol('rtmp://', props.skills.protocols.input);
+ const supportsSRT = isSupportedProtocol('srt://', props.skills.protocols.input);
+
+ if (!supportsRTMP && supportsSRT) {
+ return (
+
+
+
+
+
+ The available FFmpeg binary doesn't support any of the required protocols.
+
+
+
+
+ );
+ }
+
return (
- {settings.push.type === 'rtmp' ? : settings.push.type === 'hls' ? : null}
+ {settings.push.type === 'rtmp' && }
+ {settings.push.type === 'hls' && }
+ {settings.push.type === 'srt' && }
);
}
function PushHLS(props) {
const classes = useStyles();
- const settings = props.settings;
const config = props.config;
- const HLSs = getHLS(config, settings.push.name);
+ const HLSs = getHLS(config);
return (
@@ -559,7 +665,6 @@ function PushHLS(props) {
function PushRTMP(props) {
const classes = useStyles();
const navigate = useNavigate();
- const settings = props.settings;
const config = props.config;
let form = null;
@@ -580,7 +685,7 @@ function PushRTMP(props) {
);
} else {
- const RTMPs = getRTMP(config, settings.push.name);
+ const RTMPs = getRTMP(config);
form = (
@@ -606,6 +711,55 @@ function PushRTMP(props) {
return form;
}
+function PushSRT(props) {
+ const classes = useStyles();
+ const navigate = useNavigate();
+ const config = props.config;
+
+ let form = null;
+
+ if (config.srt.enabled === false) {
+ form = (
+
+
+
+ SRT server is not enabled
+
+
+
+
+
+
+ );
+ } else {
+ const SRTs = getSRT(config);
+
+ form = (
+
+
+
+ Send stream to this address:
+
+
+
+
+
+
+
+
+
+ Probe
+
+
+
+ );
+ }
+
+ return form;
+}
+
function Source(props) {
const classes = useStyles();
const { i18n } = useLingui();
@@ -660,9 +814,9 @@ function Source(props) {
{settings.mode === 'pull' ? (
-
+
) : (
-
+
)}
);
@@ -691,10 +845,12 @@ const func = {
initSkills,
createInputs,
getProtocolClass,
- getRTMP,
getHLS,
+ getRTMP,
+ getSRT,
isValidURL,
isAuthProtocol,
+ isSupportedProtocol,
};
export { id, name, capabilities, ffversion, SourceIcon as icon, Source as component, func };
diff --git a/src/views/Edit/Wizard/Sources/InternalSRT.js b/src/views/Edit/Wizard/Sources/InternalSRT.js
new file mode 100644
index 0000000..ad668cb
--- /dev/null
+++ b/src/views/Edit/Wizard/Sources/InternalSRT.js
@@ -0,0 +1,98 @@
+import React from 'react';
+import { useNavigate } from 'react-router-dom';
+
+import { Trans } from '@lingui/macro';
+import Button from '@mui/material/Button';
+import Grid from '@mui/material/Grid';
+import Icon from '@mui/icons-material/KeyboardTab';
+import Typography from '@mui/material/Typography';
+
+import * as S from '../../Sources/Network';
+import BoxTextarea from '../../../../misc/BoxTextarea';
+import Textarea from '../../../../misc/Textarea';
+
+const initSettings = (initialSettings) => {
+ const settings = {
+ ...S.func.initSettings(initialSettings),
+ mode: 'push',
+ };
+
+ settings.push.type = 'srt';
+
+ return settings;
+};
+
+function Source(props) {
+ const navigate = useNavigate();
+ const settings = initSettings(props.settings);
+ const config = S.func.initConfig(props.config);
+ const skills = S.func.initSkills(props.skills);
+
+ const handleChange = (newSettings) => {
+ newSettings = newSettings || settings;
+
+ const inputs = S.func.createInputs(newSettings, config, skills);
+ newSettings.address = inputs[0].address;
+
+ props.onChange(S.id, newSettings, inputs, config.rtmp.enabled);
+ };
+
+ React.useEffect(() => {
+ handleChange();
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, []);
+
+ if (config.srt.enabled === false) {
+ return (
+
+
+
+ SRT server is not enabled
+
+
+
+
+
+
+ );
+ }
+
+ const SRTs = S.func.getSRT(config, settings.push.name);
+
+ return (
+
+
+
+ Send stream to this address:
+
+
+ {SRTs.length !== 0 && (
+
+
+
+
+
+ )}
+
+ );
+}
+
+Source.defaultProps = {
+ settings: {},
+ config: null,
+ skills: null,
+ onChange: function (type, settings, inputs, ready) {},
+};
+
+function SourceIcon(props) {
+ return ;
+}
+
+const id = 'srt';
+const type = 'network';
+const name = Internal SRT server;
+const capabilities = ['audio', 'video'];
+
+export { id, type, name, capabilities, SourceIcon as icon, Source as component };
diff --git a/src/views/Edit/Wizard/Sources/index.js b/src/views/Edit/Wizard/Sources/index.js
index f7b117a..0b2905a 100644
--- a/src/views/Edit/Wizard/Sources/index.js
+++ b/src/views/Edit/Wizard/Sources/index.js
@@ -1,5 +1,6 @@
import * as AVFoundation from './AVFoundation';
import * as InternalRTMP from './InternalRTMP';
+import * as InternalSRT from './InternalSRT';
import * as Network from './Network';
import * as Raspicam from './Raspicam';
import * as V4L from './V4L';
@@ -35,6 +36,7 @@ const registry = new Registry();
registry.Register(Network);
registry.Register(InternalRTMP);
+registry.Register(InternalSRT);
//registry.Register(InternalHLS);
registry.Register(AVFoundation);
registry.Register(Raspicam);
diff --git a/src/views/Edit/Wizard/index.js b/src/views/Edit/Wizard/index.js
index 1cd580b..e2ddfc2 100644
--- a/src/views/Edit/Wizard/index.js
+++ b/src/views/Edit/Wizard/index.js
@@ -227,7 +227,16 @@ export default function Wizard(props) {
let knownSources = [];
for (let s in $skills.sources) {
if (s === 'network') {
- knownSources.push('network', 'rtmp', 'hls');
+ knownSources.push('network');
+ if ($skills.protocols.input.includes('rtmp')) {
+ knownSources.push('rtmp');
+ }
+ if ($skills.protocols.input.includes('http')) {
+ knownSources.push('hls');
+ }
+ if ($skills.protocols.input.includes('srt')) {
+ knownSources.push('srt');
+ }
} else if (s === 'video4linux2') {
knownSources.push('video4linux2');
} else if (s === 'raspicam') {