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: + + + + +