From f4d46d061d4d3130ab254c292309d27a34c79cbd Mon Sep 17 00:00:00 2001 From: patcarter883 <33055183+patcarter883@users.noreply.github.com> Date: Mon, 1 Apr 2024 07:37:35 +0000 Subject: [PATCH 01/47] Add SDP input --- src/views/Edit/Profile.js | 2 + src/views/Edit/Sources/SDP.js | 205 ++++++++++++++++++++++++++++++++ src/views/Edit/Sources/index.js | 2 + 3 files changed, 209 insertions(+) create mode 100644 src/views/Edit/Sources/SDP.js diff --git a/src/views/Edit/Profile.js b/src/views/Edit/Profile.js index 4f4bfe9..2ecd569 100644 --- a/src/views/Edit/Profile.js +++ b/src/views/Edit/Profile.js @@ -56,6 +56,7 @@ export default function Profile(props) { const load = async () => { // Add pseudo sources props.skills.sources.noaudio = []; + props.skills.sources.sdp = []; let audio = $sources.audio; @@ -121,6 +122,7 @@ export default function Profile(props) { // Add pseudo sources props.skills.sources.noaudio = []; + props.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..afe4884 --- /dev/null +++ b/src/views/Edit/Sources/SDP.js @@ -0,0 +1,205 @@ +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'); + + 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 + + + + + + + + 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 = '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; From ae985db9b11c38615821bf120f4d5599a781080f Mon Sep 17 00:00:00 2001 From: patcarter883 <33055183+patcarter883@users.noreply.github.com> Date: Tue, 2 Apr 2024 14:26:42 +0000 Subject: [PATCH 02/47] FFMpeg args for ST2110 SDP --- src/views/Edit/Sources/SDP.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/views/Edit/Sources/SDP.js b/src/views/Edit/Sources/SDP.js index afe4884..e0934e4 100644 --- a/src/views/Edit/Sources/SDP.js +++ b/src/views/Edit/Sources/SDP.js @@ -47,7 +47,10 @@ const createInputs = (settings) => { options: [], }; + input.options.push('-protocol_whitelist', 'file,udp,rtp'); + input.options.push('-buffer_size', '671088640'); + input.options.push('-thread_queue_size', '4096'); return [input]; }; From ecb39346ef9c918417acdb82ead00ae42af4b380 Mon Sep 17 00:00:00 2001 From: Ingo Oppermann Date: Fri, 20 Sep 2024 14:39:40 +0200 Subject: [PATCH 03/47] Add experimental TikTok publication service --- src/views/Publication/Services/TikTok.js | 123 +++++++++++++++++++++++ src/views/Publication/Services/index.js | 2 + 2 files changed, 125 insertions(+) create mode 100644 src/views/Publication/Services/TikTok.js diff --git a/src/views/Publication/Services/TikTok.js b/src/views/Publication/Services/TikTok.js new file mode 100644 index 0000000..64cbd62 --- /dev/null +++ b/src/views/Publication/Services/TikTok.js @@ -0,0 +1,123 @@ +import React from 'react'; + +import { faTiktok } from '@fortawesome/free-brands-svg-icons'; +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; +import { Trans } from '@lingui/macro'; +import Grid from '@mui/material/Grid'; +import Link from '@mui/material/Link'; +import TextField from '@mui/material/TextField'; + +import FormInlineButton from '../../../misc/FormInlineButton'; + +const id = 'tiktok'; +const name = 'TikTok'; +const version = '1.0'; +const stream_key_link = 'https://livecenter.tiktok.com/producer'; +const description = ( + + Transmit your Livestream to an TikTok RTMP service.{' '} + + Here{' '} + + you can find more details about the settings. + +); +const image_copyright = Please get in touch with the operator of the service and check what happens.; +const author = { + creator: { + name: 'datarhei', + link: 'https://github.com/datarhei', + }, + maintainer: { + name: 'datarhei', + link: 'https://github.com/datarhei', + }, +}; +const category = 'platform'; +const requires = { + protocols: ['rtmps'], + formats: ['flv'], + codecs: { + audio: ['aac', 'mp3'], + video: ['h264'], + }, +}; + +function ServiceIcon(props) { + return ; +} + +function init(settings) { + const initSettings = { + server_url: '', + stream_key: '', + ...settings, + }; + + return initSettings; +} + +function Service(props) { + const settings = init(props.settings); + + const handleChange = (what) => (event) => { + const value = event.target.value; + + settings[what] = value; + + const output = createOutput(settings); + + props.onChange([output], settings); + }; + + const createOutput = (settings) => { + const output = { + address: settings.server_url, + options: ['-rtmp_playpath', settings.stream_key, '-f', 'flv'], + }; + + return output; + }; + + return ( + + + Server URL} + value={settings.server_url} + onChange={handleChange('server_url')} + error={settings.server_url.includes('rtmps://') ? false : true} + helperText={settings.server_url.includes('rtmps://') ? false : 'Please enter a valid RTMPS URL.'} + /> + + + Stream key} + value={settings.stream_key} + onChange={handleChange('stream_key')} + /> + + + + GET + + + + ); +} + +Service.defaultProps = { + settings: {}, + skills: {}, + metadata: {}, + streams: [], + onChange: function (output, settings) {}, +}; + +export { id, name, version, stream_key_link, description, image_copyright, author, category, requires, ServiceIcon as icon, Service as component }; diff --git a/src/views/Publication/Services/index.js b/src/views/Publication/Services/index.js index 70405a6..a570522 100644 --- a/src/views/Publication/Services/index.js +++ b/src/views/Publication/Services/index.js @@ -29,6 +29,7 @@ import * as RTMP from './RTMP'; import * as RTSP from './RTSP'; import * as Rumble from './Rumble'; import * as SRT from './SRT'; +import * as TikTok from './TikTok'; import * as Trovo from './Trovo'; import * as Telegram from './Telegram'; import * as Twitch from './Twitch'; @@ -80,6 +81,7 @@ registry.Register(Youtube); registry.Register(Twitter); registry.Register(Twitch); registry.Register(Kick); +registry.Register(TikTok); registry.Register(Instagram); registry.Register(Vimeo); registry.Register(Restream); From eebba9e191ff02418715197eff2fdce4b9babb92 Mon Sep 17 00:00:00 2001 From: Ingo Oppermann Date: Fri, 20 Sep 2024 15:20:45 +0200 Subject: [PATCH 04/47] Allow more codecs --- src/views/Edit/Profile.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/views/Edit/Profile.js b/src/views/Edit/Profile.js index c5ee500..92f515f 100644 --- a/src/views/Edit/Profile.js +++ b/src/views/Edit/Profile.js @@ -569,7 +569,7 @@ export default function Profile(props) { type="video" streams={$sources.video.streams} profile={$profile.video} - codecs={['copy', 'h264']} + codecs={['copy', 'h264', 'hevc', 'av1', 'vp8', 'vp9']} skills={props.skills} onChange={handleEncoding('video')} /> @@ -642,7 +642,7 @@ export default function Profile(props) { type="audio" streams={$sources.video.streams} profile={$profile.audio} - codecs={['copy', 'aac', 'mp3']} + codecs={['copy', 'aac', 'mp3', 'opus']} skills={props.skills} onChange={handleEncoding('audio')} /> From c0b98b464521e2a15b7cb8fad21ff60708110d3f Mon Sep 17 00:00:00 2001 From: Ingo Oppermann Date: Fri, 20 Sep 2024 16:32:00 +0200 Subject: [PATCH 05/47] Add libaom support, fix encoder parameter --- src/misc/coders/Encoders/index.js | 6 +- src/misc/coders/Encoders/video/av1_libaom.js | 167 ++++++++++++++++++ .../coders/Encoders/video/av1_librav1e.js | 4 +- src/misc/coders/Encoders/video/vp9_libvpx.js | 8 +- 4 files changed, 178 insertions(+), 7 deletions(-) create mode 100644 src/misc/coders/Encoders/video/av1_libaom.js diff --git a/src/misc/coders/Encoders/index.js b/src/misc/coders/Encoders/index.js index a88a89d..8853715 100644 --- a/src/misc/coders/Encoders/index.js +++ b/src/misc/coders/Encoders/index.js @@ -17,12 +17,13 @@ import * as H264V4L2M2M from './video/h264_v4l2m2m'; import * as H264VAAPI from './video/h264_vaapi'; import * as HEVCVAAPI from './video/hevc_vaapi'; import * as HEVCVideoToolbox from './video/hevc_videotoolbox'; +import * as VP9VPX from './video/vp9_libvpx'; import * as VP9VAAPI from './video/vp9_vaapi'; import * as VideoCopy from './video/copy'; import * as VideoNone from './video/none'; import * as VideoRaw from './video/rawvideo'; -import * as VP9 from './video/vp9_libvpx'; import * as AV1Rav1e from './video/av1_librav1e'; +import * as AV1AOM from './video/av1_libaom'; class Registry { constructor(type) { @@ -133,8 +134,9 @@ videoRegistry.Register(H264VAAPI); videoRegistry.Register(X265); videoRegistry.Register(HEVCVAAPI); videoRegistry.Register(HEVCVideoToolbox); +videoRegistry.Register(VP9VPX); videoRegistry.Register(VP9VAAPI); -videoRegistry.Register(VP9); +videoRegistry.Register(AV1AOM); videoRegistry.Register(AV1Rav1e); videoRegistry.Register(VideoRaw); diff --git a/src/misc/coders/Encoders/video/av1_libaom.js b/src/misc/coders/Encoders/video/av1_libaom.js new file mode 100644 index 0000000..8ed846b --- /dev/null +++ b/src/misc/coders/Encoders/video/av1_libaom.js @@ -0,0 +1,167 @@ +import React from 'react'; + +import Grid from '@mui/material/Grid'; +import MenuItem from '@mui/material/MenuItem'; +import Typography from '@mui/material/Typography'; + +import { Trans } from '@lingui/macro'; + +import Select from '../../../Select'; +import Video from '../../settings/Video'; +import Helper from '../../helper'; + +function init(initialState) { + const state = { + bitrate: '4096', + fps: '25', + fps_mode: 'auto', + gop: '2', + usage: 'realtime', + ...initialState, + }; + + return state; +} + +function createMapping(settings, stream, skills) { + stream = Helper.InitStream(stream); + skills = Helper.InitSkills(skills); + + const local = [ + '-codec:v', + 'libaom-av1', + '-b:v', + `${settings.bitrate}k`, + '-maxrate:v', + `${settings.bitrate}k`, + '-bufsize:v', + `${settings.bitrate}k`, + '-r', + `${settings.fps}`, + '-pix_fmt', + 'yuv420p', + '-usage', + `${settings.usage}`, + ]; + + if (settings.gop !== 'auto') { + local.push( + '-g', + `${Math.round(parseInt(settings.fps) * parseInt(settings.gop)).toFixed(0)}`, + '-keyint_min', + `${Math.round(parseInt(settings.fps) * parseInt(settings.gop)).toFixed(0)}`, + ); + } + + if (skills.ffmpeg.version_major >= 5) { + local.push('-fps_mode', `${settings.fps_mode}`); + } + + const mapping = { + global: [], + local: local, + filter: [], + }; + + return mapping; +} + +function Usage(props) { + return ( + + + + Quality and compression efficiency vs speed trade-off. + + + ); +} + +Usage.defaultProps = { + value: 'realtime', + onChange: function (event) {}, +}; + +function Coder(props) { + const settings = init(props.settings); + const stream = Helper.InitStream(props.stream); + const skills = Helper.InitSkills(props.skills); + + const handleChange = (newSettings) => { + let automatic = false; + if (!newSettings) { + newSettings = settings; + automatic = true; + } + + props.onChange(newSettings, createMapping(newSettings, stream, skills), automatic); + }; + + const update = (what) => (event) => { + const newSettings = { + ...settings, + [what]: event.target.value, + }; + + handleChange(newSettings); + }; + + React.useEffect(() => { + handleChange(null); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + + return ( + + + + + + + + + + + {skills.ffmpeg.version_major >= 5 && ( + + + + )} + + + + + ); +} + +Coder.defaultProps = { + stream: {}, + settings: {}, + skills: {}, + onChange: function (settings, mapping) {}, +}; + +const coder = 'libaom-av1'; +const name = 'AV1 (libaom)'; +const codec = 'av1'; +const type = 'video'; +const hwaccel = false; + +function summarize(settings) { + return `${name}, ${settings.bitrate} kbit/s, ${settings.fps} FPS, Usage: ${settings.usage}`; +} + +function defaults(stream, skills) { + const settings = init({}); + + return { + settings: settings, + mapping: createMapping(settings, stream, skills), + }; +} + +export { coder, name, codec, type, hwaccel, summarize, defaults, Coder as component }; diff --git a/src/misc/coders/Encoders/video/av1_librav1e.js b/src/misc/coders/Encoders/video/av1_librav1e.js index b350d85..fc1ce36 100644 --- a/src/misc/coders/Encoders/video/av1_librav1e.js +++ b/src/misc/coders/Encoders/video/av1_librav1e.js @@ -20,8 +20,8 @@ function init(initialState) { qp: '-1', speed: '-1', tiles: '0', - tile_rows: '0', - tile_columns: '0', + tile_rows: '1', + tile_columns: '2', params: '', ...initialState, }; diff --git a/src/misc/coders/Encoders/video/vp9_libvpx.js b/src/misc/coders/Encoders/video/vp9_libvpx.js index 42d95c4..aa7da96 100644 --- a/src/misc/coders/Encoders/video/vp9_libvpx.js +++ b/src/misc/coders/Encoders/video/vp9_libvpx.js @@ -32,8 +32,10 @@ function createMapping(settings, stream, skills) { `${settings.bitrate}k`, '-r', `${settings.fps}`, - '-sc_threshold', - '0', + '-deadline', + 'realtime', + '-quality', + 'realtime', '-pix_fmt', 'yuv420p', ]; @@ -123,7 +125,7 @@ const type = 'video'; const hwaccel = false; function summarize(settings) { - return `${name}, ${settings.bitrate} kbit/s, ${settings.fps} FPS, Preset: ${settings.preset}, Profile: ${settings.profile}`; + return `${name}, ${settings.bitrate} kbit/s, ${settings.fps} FPS`; } function defaults(stream, skills) { From 8db285fe89d0dc809896e887a2e44a17bc1ae5ad Mon Sep 17 00:00:00 2001 From: Ingo Oppermann Date: Fri, 20 Sep 2024 16:38:06 +0200 Subject: [PATCH 06/47] Add libfdk_aac support --- src/misc/coders/Encoders/audio/aac_libfdk.js | 102 +++++++++++++++++++ src/misc/coders/Encoders/index.js | 2 + 2 files changed, 104 insertions(+) create mode 100644 src/misc/coders/Encoders/audio/aac_libfdk.js diff --git a/src/misc/coders/Encoders/audio/aac_libfdk.js b/src/misc/coders/Encoders/audio/aac_libfdk.js new file mode 100644 index 0000000..5555687 --- /dev/null +++ b/src/misc/coders/Encoders/audio/aac_libfdk.js @@ -0,0 +1,102 @@ +import React from 'react'; + +import Grid from '@mui/material/Grid'; + +import Audio from '../../settings/Audio'; +import Helper from '../../helper'; + +function init(initialState) { + const state = { + bitrate: '64', + ...initialState, + }; + + return state; +} + +function createMapping(settings, stream, skills) { + stream = Helper.InitStream(stream); + skills = Helper.InitSkills(skills); + + const local = ['-codec:a', 'libfdk_aac', '-b:a', `${settings.bitrate}k`, '-shortest']; + + if (stream.codec === 'aac') { + local.push('-bsf:a', 'aac_adtstoasc'); + } + + const mapping = { + global: [], + local: local, + filter: [], + }; + + return mapping; +} + +function Coder(props) { + const settings = init(props.settings); + const stream = Helper.InitStream(props.stream); + const skills = Helper.InitSkills(props.skills); + + const handleChange = (newSettings) => { + let automatic = false; + if (!newSettings) { + newSettings = settings; + automatic = true; + } + + props.onChange(newSettings, createMapping(newSettings, stream, skills), automatic); + }; + + const update = (what) => (event) => { + const value = event.target.value; + + const newSettings = { + ...settings, + [what]: value, + }; + + handleChange(newSettings); + }; + + React.useEffect(() => { + handleChange(null); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + + return ( + + + + + + ); +} + +Coder.defaultProps = { + stream: {}, + settings: {}, + skills: {}, + onChange: function (settings, mapping) {}, +}; + +const coder = 'libfdk_aac'; +const name = 'AAC (libfdk)'; +const codec = 'aac'; +const type = 'audio'; +const hwaccel = false; + +function summarize(settings) { + return `${name}, ${settings.bitrate} kbit/s`; +} + +function defaults(stream, skills) { + const settings = init({}); + + return { + settings: settings, + mapping: createMapping(settings, stream, skills), + }; +} + +export { coder, name, codec, type, hwaccel, summarize, defaults, Coder as component }; diff --git a/src/misc/coders/Encoders/index.js b/src/misc/coders/Encoders/index.js index 8853715..e6bf7ee 100644 --- a/src/misc/coders/Encoders/index.js +++ b/src/misc/coders/Encoders/index.js @@ -2,6 +2,7 @@ import * as AudioCopy from './audio/copy'; import * as AudioNone from './audio/none'; import * as AAC from './audio/aac'; import * as AACAudioToolbox from './audio/aac_audiotoolbox'; +import * as AACFDK from './audio/aac_libfdk'; import * as Libopus from './audio/opus_libopus'; import * as Libvorbis from './audio/vorbis_libvorbis'; import * as MP3 from './audio/mp3_libmp3lame'; @@ -115,6 +116,7 @@ audioRegistry.Register(AudioCopy); audioRegistry.Register(AudioNone); audioRegistry.Register(AAC); audioRegistry.Register(AACAudioToolbox); +audioRegistry.Register(AACFDK); audioRegistry.Register(MP3); audioRegistry.Register(Opus); audioRegistry.Register(Libopus); From c3632068a6ca1f85fb2a7bb15d2d8f18c9d3c86c Mon Sep 17 00:00:00 2001 From: Jan Stabenow Date: Tue, 24 Sep 2024 17:34:47 +0200 Subject: [PATCH 07/47] Add h264 player stream for unsupported codecs (experimental) --- src/misc/Player/videojs.js | 20 +++ src/misc/controls/Preview.js | 81 +++++++++++++ src/utils/metadata.js | 10 ++ src/utils/restreamer.js | 229 ++++++++++++++++++++++++----------- src/views/Edit/index.js | 22 ++++ src/views/Main/index.js | 66 +++++++++- 6 files changed, 353 insertions(+), 75 deletions(-) create mode 100644 src/misc/controls/Preview.js diff --git a/src/misc/Player/videojs.js b/src/misc/Player/videojs.js index 52b3d48..0bbb08d 100644 --- a/src/misc/Player/videojs.js +++ b/src/misc/Player/videojs.js @@ -14,6 +14,15 @@ export default function VideoJS(props) { const playerRef = React.useRef(null); const { options, onReady } = props; + const retryVideo = () => { + const player = playerRef.current; + if (player) { + player.error(null); // Clear the error + player.src(options.sources); // Reload the source + player.play(); // Attempt to play again + } + }; + React.useEffect(() => { // make sure Video.js player is only initialized once if (!playerRef.current) { @@ -32,6 +41,17 @@ export default function VideoJS(props) { } player.addClass('video-js'); player.addClass('vjs-16-9'); + + // retry on MEDIA_ERR_NETWORK = 2 + let retry_count = 0; + player.on('error', () => { + const error = player.error(); + console.log(error); + if (error && (error.code === 2 || error.code === 4) && retry_count < 10) { + retry_count += 1; + setTimeout(retryVideo, 2000); + } + }); } else { // you can update player here [update player through props] // const player = playerRef.current; diff --git a/src/misc/controls/Preview.js b/src/misc/controls/Preview.js new file mode 100644 index 0000000..1078b66 --- /dev/null +++ b/src/misc/controls/Preview.js @@ -0,0 +1,81 @@ +import React from 'react'; + +import { Trans } from '@lingui/macro'; +import Grid from '@mui/material/Grid'; +import MenuItem from '@mui/material/MenuItem'; +import Typography from '@mui/material/Typography'; + +import Checkbox from '../Checkbox'; +import Select from '../Select'; + +function init(settings) { + const initSettings = { + enable: true, + video_encoder: 'libx264', + audio_encoder: 'aac', + ...settings, + }; + + return initSettings; +} + +export default function Control(props) { + const settings = init(props.settings); + const encoders = props.encoders; + + // Set the defaults + React.useEffect(() => { + props.onChange(settings, true); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + + const handleChange = (what) => (event) => { + const value = event.target.value; + + if (['enable'].includes(what)) { + settings[what] = !settings[what]; + } else { + settings[what] = value; + } + + props.onChange(settings, false); + }; + + return ( + + + Enable browser-compatible H.264 stream} checked={settings.enable} onChange={handleChange('enable')} /> + + + + + The H.264 encoder used. + + + + ); +} + +Control.defaulProps = { + settings: {}, + onChange: function (settings, automatic) {}, +}; diff --git a/src/utils/metadata.js b/src/utils/metadata.js index fc0eea0..a2b34a3 100644 --- a/src/utils/metadata.js +++ b/src/utils/metadata.js @@ -285,6 +285,11 @@ const defaultIngestMetadata = { enable: true, interval: 60, }, + preview: { + enable: false, + video_encoder: 'libx264', + audio_encoder: 'aac', + }, limits: { cpu_usage: 0, memory_mbytes: 0, @@ -472,6 +477,11 @@ const mergeIngestMetadata = (metadata, base) => { ...metadata.control.snapshot, }; + metadata.control.preview = { + ...base.control.preview, + ...metadata.control.preview, + }; + if (!Array.isArray(metadata.sources)) { metadata.sources = []; } else { diff --git a/src/utils/restreamer.js b/src/utils/restreamer.js index 5fcb4ef..c4cbf88 100644 --- a/src/utils/restreamer.js +++ b/src/utils/restreamer.js @@ -1507,15 +1507,21 @@ class Restreamer { } // Get the ingest progress - async GetIngestProgress(channelid) { + async GetIngestProgress(channelid, what) { + // [what] for custom id extentions const channel = this.GetChannel(channelid); if (channel === null) { return this._getProgressFromState(null); } - const state = await this._getProcessState(channel.id); - - return this._getProgressFromState(state); + if (!what) { + const state = await this._getProcessState(`${channel.id}`); + return this._getProgressFromState(state); + } else { + // id=abc, what=_preview = abc_preview + const state = await this._getProcessState(`${channel.id}${what}`); + return this._getProgressFromState(state); + } } // Get the ingest log @@ -1716,71 +1722,49 @@ class Restreamer { // fix Malformed AAC bitstream detected for hls version 7 let hls_aac_adtstoasc = false; - const getHLSParams = (lhls, version) => { - if (lhls) { - // lhls - return [ - ['f', 'dash'], - ['strict', 'experimental'], - ['hls_playlist', '1'], - ['init_seg_name', `init-${channel.channelid}.$ext$`], - ['media_seg_name', `chunk-${channel.channelid}-$Number%05d$.$ext$`], - ['master_m3u8_publish_rate', '1'], - ['adaptation_sets', 'id=0,streams=v id=1,streams=a'], - ['lhls', '1'], - ['streaming', '1'], - ['seg_duration', '' + parseInt(control.hls.segmentDuration)], - ['frag_duration', '0.5'], - ['use_template', '1'], - ['remove_at_exit', '0'], - ['window_size', '' + parseInt(control.hls.listSize)], - ['http_persistent', '0'], - ]; - } else { - // hls - switch (version) { - case 6: - return [ - ['f', 'hls'], - ['start_number', '0'], - ['hls_time', '' + parseInt(control.hls.segmentDuration)], - ['hls_list_size', '' + parseInt(control.hls.listSize)], - ['hls_flags', 'append_list+delete_segments+program_date_time+independent_segments+temp_file'], - ['hls_delete_threshold', '4'], - ['hls_segment_filename', hls_segment_filename], - ]; - case 7: - // fix Malformed AAC bitstream detected for hls version 7 - if (output.options.includes('-codec:a') && output.options.includes('copy')) { - if (!tee_muxer) { - output.options.push('-bsf:a', 'aac_adtstoasc'); - } - hls_aac_adtstoasc = true; + const getHLSParams = (version) => { + switch (version) { + case 6: + return [ + ['f', 'hls'], + ['start_number', '0'], + ['hls_time', '' + parseInt(control.hls.segmentDuration)], + ['hls_list_size', '' + parseInt(control.hls.listSize)], + ['hls_flags', 'append_list+delete_segments+program_date_time+independent_segments+temp_file'], + ['hls_delete_threshold', '4'], + ['hls_segment_filename', hls_segment_filename], + ]; + case 7: + // fix Malformed AAC bitstream detected for hls version 7 + if (output.options.includes('-codec:a') && output.options.includes('copy')) { + if (!tee_muxer) { + output.options.push('-bsf:a', 'aac_adtstoasc'); } - return [ - ['f', 'hls'], - ['start_number', '0'], - ['hls_time', '' + parseInt(control.hls.segmentDuration)], - ['hls_list_size', '' + parseInt(control.hls.listSize)], - ['hls_flags', 'append_list+delete_segments+program_date_time+independent_segments+temp_file'], - ['hls_delete_threshold', '4'], - ['hls_segment_type', 'fmp4'], - ['hls_fmp4_init_filename', hls_fmp4_init_filename], - ['hls_fmp4_init_resend', '1'], - ['hls_segment_filename', hls_segment_filename], - ]; - // case 3 - default: - return [ - ['f', 'hls'], - ['start_number', '0'], - ['hls_time', '' + parseInt(control.hls.segmentDuration)], - ['hls_list_size', '' + parseInt(control.hls.listSize)], - ['hls_flags', 'append_list+delete_segments+program_date_time+temp_file'], - ['hls_delete_threshold', '4'], - ['hls_segment_filename', hls_segment_filename], - ]; - } + hls_aac_adtstoasc = true; + } + return [ + ['f', 'hls'], + ['start_number', '0'], + ['hls_time', '' + parseInt(control.hls.segmentDuration)], + ['hls_list_size', '' + parseInt(control.hls.listSize)], + ['hls_flags', 'append_list+delete_segments+program_date_time+independent_segments+temp_file'], + ['hls_delete_threshold', '4'], + ['hls_segment_type', 'fmp4'], + ['hls_fmp4_init_filename', hls_fmp4_init_filename], + ['hls_fmp4_init_resend', '1'], + ['hls_segment_filename', hls_segment_filename], + ]; + // case 3 + default: + return [ + ['f', 'hls'], + ['start_number', '0'], + ['hls_time', '' + parseInt(control.hls.segmentDuration)], + ['hls_list_size', '' + parseInt(control.hls.listSize)], + ['hls_flags', 'append_list+delete_segments+program_date_time+temp_file'], + ['hls_delete_threshold', '4'], + ['hls_segment_filename', hls_segment_filename], + ]; } }; const hls_params_raw = getHLSParams(control.hls.lhls, control.hls.version); @@ -1945,8 +1929,8 @@ class Restreamer { }, ], options: ['-err_detect', 'ignore_err'], - autostart: control.process.autostart, - reconnect: true, + autostart: control.snapshot.enable ? control.process.autostart : false, + reconnect: control.snapshot.enable ? true : false, reconnect_delay_seconds: parseInt(control.snapshot.interval), stale_timeout_seconds: 30, }; @@ -1959,6 +1943,99 @@ class Restreamer { return [val, null]; } + // Upsert the ingest browser playback process (preview) + async UpsertIngestPreview(channelid, control) { + const channel = this.GetChannel(channelid); + if (channel === null) { + return [null, { message: 'Unknown channel ID' }]; + } + + // Set hls storage endpoint + const hlsStorage = control.hls.storage; + + // Set encoder settings + const video_encoder = control.preview.video_encoder; + const audio_encoder = control.preview.audio_encoder; + + const preview = { + type: 'ffmpeg', + id: channel.id + '_h264', + reference: channel.channelid, + input: [ + { + id: 'input_0', + address: `{${hlsStorage}}/${channel.channelid}.m3u8`, + options: [], + }, + ], + output: [ + { + id: 'output_0', + address: `{memfs}/${channel.channelid}_output_0_h264.m3u8`, + options: [ + '-c:v', + `${video_encoder}`, + '-r', + '25', + '-g', + '50', + '-sc_threshold', + '0', + '-pix_fmt', + 'yuv420p', + '-c:a', + `${audio_encoder}`, + '-f', + 'hls', + '-start_number', + '0', + '-hls_time', + '2', + '-hls_list_size', + '6', + '-hls_flags', + 'append_list+delete_segments+program_date_time+temp_file', + '-hls_delete_threshold', + '4', + '-hls_segment_filename', + `{memfs}/${channel.channelid}_output_0_h264_%04d.ts`, + '-master_pl_name', + `${channel.channelid}_h264.m3u8`, + '-master_pl_publish_rate', + '2', + ], + cleanup: [ + { + pattern: `memfs:/${channel.channelid}_h264.m3u8`, + purge_on_delete: true, + }, + { + pattern: `memfs:/${channel.channelid}_output_0_h264.m3u8`, + purge_on_delete: true, + }, + { + pattern: `memfs:/${channel.channelid}_output_0_h264_*.ts`, + max_files: 12, + purge_on_delete: true, + }, + ], + }, + ], + options: ['-err_detect', 'ignore_err'], + autostart: control.preview.enable ? control.process.autostart : false, + reconnect: control.preview.enable ? control.process.reconnect : false, + reconnect_delay_seconds: 2, + stale_timeout_seconds: 5, + }; + + const [val, err] = await this._upsertProcess(channel.id + '_h264', preview); + if (err !== null) { + return [val, err]; + } + + return [val, null]; + } + // Check whether the manifest of the ingest process is available async HasIngestFiles(channelid) { const channel = this.GetChannel(channelid); @@ -3410,6 +3487,8 @@ class Restreamer { command: [], cpu: 0, memory: 0, + video_codec: '', + audio_codec: '', }; if (state === null) { @@ -3452,8 +3531,16 @@ class Restreamer { progress.dup = state.progress.dup || 0; progress.cpu = state.cpu_usage || 0; progress.memory = state.memory_bytes || 0; - } + // check av codec @ preview + for (const o in state.progress.outputs) { + if (state.progress.outputs[o].type === 'video') { + progress.video_codec = state.progress.outputs[o].codec; + } else if (state.progress.outputs[o].type === 'audio') { + progress.audio_codec = state.progress.outputs[o].codec; + } + } + } return progress; } diff --git a/src/views/Edit/index.js b/src/views/Edit/index.js index a5fa17c..cbea87c 100644 --- a/src/views/Edit/index.js +++ b/src/views/Edit/index.js @@ -29,6 +29,7 @@ import Paper from '../../misc/Paper'; import PaperHeader from '../../misc/PaperHeader'; import PaperFooter from '../../misc/PaperFooter'; import PaperThumb from '../../misc/PaperThumb'; +import PreviewControl from '../../misc/controls/Preview'; import ProcessControl from '../../misc/controls/Process'; import Profile from './Profile'; import ProfileSummary from './ProfileSummary'; @@ -322,6 +323,12 @@ export default function Edit(props) { notify.Dispatch('error', 'save:ingest', i18n._(t`Failed to update ingest snapshot process (${err.message})`)); } + // Create/update the ingest preview process + [, err] = await props.restreamer.UpsertIngestPreview(_channelid, control); + if (err !== null) { + notify.Dispatch('error', 'save:ingest', i18n._(t`Failed to update ingest preview process (${err.message})`)); + } + // Create/update the player res = await props.restreamer.UpdatePlayer(_channelid); if (res === false) { @@ -549,6 +556,21 @@ export default function Edit(props) { + + + Player Playback + + + + + + + + Process diff --git a/src/views/Main/index.js b/src/views/Main/index.js index d242617..35a3956 100644 --- a/src/views/Main/index.js +++ b/src/views/Main/index.js @@ -38,7 +38,7 @@ const useStyles = makeStyles((theme) => ({ playerL1: { //padding: '4px 1px 4px 8px', paddingTop: 10, - paddingLeft: 18 + paddingLeft: 18, }, playerL2: { position: 'relative', @@ -69,6 +69,7 @@ export default function Main(props) { progress: {}, state: 'disconnected', onConnect: null, + preview: null, }); const [$metadata, setMetadata] = React.useState(M.getDefaultIngestMetadata()); const [$processDetails, setProcessDetails] = React.useState({ @@ -149,6 +150,15 @@ export default function Main(props) { }, 100); state.onConnect = null; } + // check av codec @ preview + if (state.progress.video_codec !== 'h264' && $state.preview === null) { + const preview_progress = await props.restreamer.GetIngestProgress(`${_channelid}`, '_preview'); + if (preview_progress) { + state.preview = false; + } else { + state.preview = true; + } + } } if ($metadata.control.rtmp.enable) { @@ -287,11 +297,16 @@ export default function Main(props) { const storage = $metadata.control.hls.storage; const channel = props.restreamer.GetChannel(_channelid); const manifest = props.restreamer.GetChannelAddress('hls+' + storage, _channelid); + const manifest_preview = props.restreamer.GetChannelAddress('hls+' + storage, `${_channelid}_h264`); const poster = props.restreamer.GetChannelAddress('snapshot+' + storage, _channelid); - let title = Main channel; + let title = {$state.progress.video_codec} - Main channel; if (channel && channel.name && channel.name.length !== 0) { - title = channel.name; + if ($state.progress.video_codec) { + title = `${$state.progress.video_codec} - ${channel.name}`; + } else { + title = `${channel.name}`; + } } return ( @@ -382,9 +397,42 @@ export default function Main(props) { )} )} - {$state.state === 'connected' && ( + {$state.state === 'connected' && $state.progress.video_codec === 'h264' && ( )} + {$state.state === 'connected' && $state.progress.video_codec !== 'h264' && $metadata.control.preview.enable && ( + + )} + {$state.state === 'connected' && $state.progress.video_codec !== 'h264' && !$metadata.control.preview.enable && ( + + + + + + + No H.264 Stream availabe. + + + + + + Please{' '} + navigate(`/${_channelid}/edit`)}> + edit + {' '} + this channel and enable the browser-compatible H.264 stream in the "Processing & Control" area: + + + + + )} @@ -433,6 +481,16 @@ export default function Main(props) { > Snapshot + {$metadata.control.preview.enable && ( + + HLS @ H.264 + + )} From a57cd63d818ca3e406f853df36b12efa426f756c Mon Sep 17 00:00:00 2001 From: Jan Stabenow Date: Tue, 24 Sep 2024 18:37:58 +0200 Subject: [PATCH 08/47] Add chip + small fixes --- src/misc/Player/videojs.js | 8 +++--- src/theme/components/chip.js | 34 +++++++++++++++++++++++++ src/theme/index.js | 2 ++ src/utils/restreamer.js | 2 +- src/views/Main/index.js | 49 +++++++++++++++++++++++++++++------- 5 files changed, 80 insertions(+), 15 deletions(-) create mode 100644 src/theme/components/chip.js diff --git a/src/misc/Player/videojs.js b/src/misc/Player/videojs.js index 0bbb08d..502ebc2 100644 --- a/src/misc/Player/videojs.js +++ b/src/misc/Player/videojs.js @@ -42,13 +42,10 @@ export default function VideoJS(props) { player.addClass('video-js'); player.addClass('vjs-16-9'); - // retry on MEDIA_ERR_NETWORK = 2 - let retry_count = 0; + // retry on MEDIA_ERR_NETWORK = 2 || 4 player.on('error', () => { const error = player.error(); - console.log(error); - if (error && (error.code === 2 || error.code === 4) && retry_count < 10) { - retry_count += 1; + if (error && (error.code === 2 || error.code === 4)) { setTimeout(retryVideo, 2000); } }); @@ -58,6 +55,7 @@ export default function VideoJS(props) { // player.autoplay(options.autoplay); // player.src(options.sources); } + // eslint-disable-next-line }, [options, videoRef, onReady, props.type]); // Dispose the Video.js player when the functional component unmounts diff --git a/src/theme/components/chip.js b/src/theme/components/chip.js new file mode 100644 index 0000000..4caa491 --- /dev/null +++ b/src/theme/components/chip.js @@ -0,0 +1,34 @@ +/* eslint-disable import/no-anonymous-default-export */ +import base from '../base'; + +const root = { + textAlign: 'center', + textTransform: 'uppercase', + userSelect: 'none', +}; + +const outlined = { + base: { + borderRadius: 4, + border: 'unset', + height: 'auto', + '& .MuiChip-label': { + padding: '.1em .7em .1em .7em', + margin: 0, + fontSize: '.9rem' + }, + marginRight: '.5em' + }, + primary: { + color: base.palette.text.primary, + backgroundColor: base.palette.background.box_default, + }, +}; + +export default { + styleOverrides: { + root: { ...root }, + outlined: { ...outlined.base }, + outlinedPrimary: { ...outlined.primary }, + }, +}; diff --git a/src/theme/index.js b/src/theme/index.js index 75dcb6b..696730d 100644 --- a/src/theme/index.js +++ b/src/theme/index.js @@ -11,6 +11,7 @@ import backdrop from './components/backdrop'; import box from './components/box'; import button from './components/button'; import checkobox from './components/checkbox'; +import chip from './components/chip'; import ctypography from './components/typography'; import dialog from './components/dialog'; import divider from './components/divider'; @@ -58,6 +59,7 @@ const theme = createTheme({ MuiBox: { ...box }, MuiButton: { ...button }, MuiCheckbox: { ...checkobox }, + MuiChip: { ...chip }, MuiDialog: { ...dialog }, MuiDivider: { ...divider }, MuiFab: { ...fab }, diff --git a/src/utils/restreamer.js b/src/utils/restreamer.js index c4cbf88..c9a6e10 100644 --- a/src/utils/restreamer.js +++ b/src/utils/restreamer.js @@ -1767,7 +1767,7 @@ class Restreamer { ]; } }; - const hls_params_raw = getHLSParams(control.hls.lhls, control.hls.version); + const hls_params_raw = getHLSParams(control.hls.version); // 3.3 Use strftime for DiskFS if (control.hls.storage && control.hls.storage === 'diskfs') { diff --git a/src/views/Main/index.js b/src/views/Main/index.js index 35a3956..3188927 100644 --- a/src/views/Main/index.js +++ b/src/views/Main/index.js @@ -3,6 +3,7 @@ import { useNavigate, useParams } from 'react-router-dom'; import { Trans } from '@lingui/macro'; import makeStyles from '@mui/styles/makeStyles'; +import Chip from '@mui/material/Chip'; import CircularProgress from '@mui/material/CircularProgress'; import Grid from '@mui/material/Grid'; import Link from '@mui/material/Link'; @@ -300,10 +301,14 @@ export default function Main(props) { const manifest_preview = props.restreamer.GetChannelAddress('hls+' + storage, `${_channelid}_h264`); const poster = props.restreamer.GetChannelAddress('snapshot+' + storage, _channelid); - let title = {$state.progress.video_codec} - Main channel; + let title = Main channel; if (channel && channel.name && channel.name.length !== 0) { if ($state.progress.video_codec) { - title = `${$state.progress.video_codec} - ${channel.name}`; + title = ( + <> + {channel.name} + + ); } else { title = `${channel.name}`; } @@ -326,7 +331,7 @@ export default function Main(props) { className={classes.playerL3} justifyContent="center" alignItems="center" - spacing={1} + spacing={2} > @@ -342,7 +347,7 @@ export default function Main(props) { className={classes.playerL3} justifyContent="center" alignItems="center" - spacing={1} + spacing={2} > @@ -361,7 +366,7 @@ export default function Main(props) { className={classes.playerL3} justifyContent="center" alignItems="center" - spacing={1} + spacing={2} > @@ -397,7 +402,7 @@ export default function Main(props) { )} )} - {$state.state === 'connected' && $state.progress.video_codec === 'h264' && ( + {$state.state === 'connected' && $state.progress.video_codec === 'h264'&& !$metadata.control.preview.enable && ( )} {$state.state === 'connected' && $state.progress.video_codec !== 'h264' && $metadata.control.preview.enable && ( @@ -410,7 +415,8 @@ export default function Main(props) { className={classes.playerL3} justifyContent="center" alignItems="center" - spacing={1} + textAlign={"center"} + spacing={2} > @@ -420,14 +426,39 @@ export default function Main(props) { No H.264 Stream availabe. - + Please{' '} navigate(`/${_channelid}/edit`)}> edit {' '} - this channel and enable the browser-compatible H.264 stream in the "Processing & Control" area: + this channel and enable the browser-compatible H.264 stream in the "Processing & Control" area. + + + + + )} + {$state.state === 'connected' && $state.progress.video_codec === 'h264' && $metadata.control.preview.enable && ( + + + + + + + + Please{' '} + navigate(`/${_channelid}/edit`)}> + edit + {' '} + this channel and disable the second H.264 stream in the "Processing & Control" area. From 2ecf252a573963c15018519dd5a28e90841a6ef2 Mon Sep 17 00:00:00 2001 From: Ingo Oppermann Date: Wed, 25 Sep 2024 15:03:18 +0200 Subject: [PATCH 09/47] Fix settings name, disable select --- src/misc/controls/Preview.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/misc/controls/Preview.js b/src/misc/controls/Preview.js index 1078b66..580537e 100644 --- a/src/misc/controls/Preview.js +++ b/src/misc/controls/Preview.js @@ -47,7 +47,7 @@ export default function Control(props) { Enable browser-compatible H.264 stream} checked={settings.enable} onChange={handleChange('enable')} /> - Video Codec} value={settings.video_encoder} disabled={!settings.enable} onChange={handleChange('video_encoder')}> H.264 (libx264) @@ -77,5 +77,6 @@ export default function Control(props) { Control.defaulProps = { settings: {}, + encoders: [], onChange: function (settings, automatic) {}, }; From ee50071c8dc0871020e0aa16d6ceabb334ccef9a Mon Sep 17 00:00:00 2001 From: Jan Stabenow Date: Thu, 26 Sep 2024 08:34:06 +0200 Subject: [PATCH 10/47] Mod uses params instead of defaultProps --- src/Footer.js | 41 ++- src/Header.js | 83 +++--- src/RestreamerUI.js | 16 +- src/Router.js | 30 +- src/index.js | 2 +- src/misc/ActionButton.js | 48 ++- src/misc/BoxText.js | 23 +- src/misc/BoxTextarea.js | 8 +- src/misc/Changelog.js | 27 +- src/misc/ChannelList.js | 70 ++--- src/misc/Checkbox.js | 15 +- src/misc/ColorPicker.js | 16 +- src/misc/CopyButton.js | 9 +- src/misc/Duration.js | 10 +- src/misc/EncodingSelect.js | 73 +++-- src/misc/Env.js | 6 +- src/misc/Filesize.js | 17 +- src/misc/FilterSelect.js | 25 +- src/misc/FormInlineButton.js | 34 ++- src/misc/LanguageSelect.js | 8 +- src/misc/ModalContent.js | 26 +- src/misc/MultiSelect.js | 27 +- src/misc/MultiSelectOption.js | 14 +- src/misc/Number.js | 14 +- src/misc/Paper.js | 51 ++-- src/misc/PaperContent.js | 11 +- src/misc/PaperFooter.js | 11 +- src/misc/PaperHeader.js | 33 +- src/misc/PaperThumb.js | 10 +- src/misc/Password.js | 58 ++-- src/misc/Player/index.js | 72 +++-- src/misc/Player/videojs.js | 13 +- src/misc/Progress.js | 105 +++++-- src/misc/Select.js | 18 +- src/misc/SelectCustom.js | 85 +++--- src/misc/TabPanel.js | 5 +- src/misc/TabsHorizontal.js | 12 +- src/misc/TabsVerticalGrid.js | 8 +- src/misc/TextField.js | 47 ++- src/misc/TextFieldCopy.js | 44 ++- src/misc/Textarea.js | 85 +++--- src/misc/UploadButton.js | 40 +-- src/misc/coders/Decoders/audio/default.js | 17 +- src/misc/coders/Decoders/video/av1_cuvid.js | 17 +- src/misc/coders/Decoders/video/default.js | 17 +- src/misc/coders/Decoders/video/h264_cuvid.js | 17 +- src/misc/coders/Decoders/video/h264_mmal.js | 17 +- src/misc/coders/Decoders/video/hevc_cuvid.js | 17 +- src/misc/coders/Decoders/video/mjpeg_cuvid.js | 17 +- src/misc/coders/Decoders/video/mpeg1_cuvid.js | 17 +- src/misc/coders/Decoders/video/mpeg2_cuvid.js | 17 +- src/misc/coders/Decoders/video/mpeg2_mmal.js | 17 +- src/misc/coders/Decoders/video/mpeg4_cuvid.js | 17 +- src/misc/coders/Decoders/video/mpeg4_mmal.js | 17 +- src/misc/coders/Decoders/video/nvdec.js | 17 +- src/misc/coders/Decoders/video/vc1_cuvid.js | 17 +- src/misc/coders/Decoders/video/vc1_mmal.js | 17 +- .../coders/Decoders/video/videotoolbox.js | 17 +- src/misc/coders/Decoders/video/vp8_cuvid.js | 17 +- src/misc/coders/Decoders/video/vp9_cuvid.js | 17 +- src/misc/coders/Encoders/audio/aac.js | 17 +- .../coders/Encoders/audio/aac_audiotoolbox.js | 17 +- src/misc/coders/Encoders/audio/aac_libfdk.js | 17 +- src/misc/coders/Encoders/audio/copy.js | 17 +- .../coders/Encoders/audio/mp3_libmp3lame.js | 17 +- src/misc/coders/Encoders/audio/none.js | 17 +- src/misc/coders/Encoders/audio/opus.js | 52 ++-- .../coders/Encoders/audio/opus_libopus.js | 17 +- src/misc/coders/Encoders/audio/vorbis.js | 17 +- .../coders/Encoders/audio/vorbis_libvorbis.js | 17 +- src/misc/coders/Encoders/video/av1_libaom.js | 26 +- .../coders/Encoders/video/av1_librav1e.js | 26 +- src/misc/coders/Encoders/video/copy.js | 17 +- .../coders/Encoders/video/h264_libx264.js | 35 +-- src/misc/coders/Encoders/video/h264_nvenc.js | 53 +--- src/misc/coders/Encoders/video/h264_omx.js | 17 +- .../coders/Encoders/video/h264_v4l2m2m.js | 17 +- src/misc/coders/Encoders/video/h264_vaapi.js | 35 +-- .../Encoders/video/h264_videotoolbox.js | 26 +- .../coders/Encoders/video/hevc_libx265.js | 35 +-- src/misc/coders/Encoders/video/hevc_vaapi.js | 35 +-- .../Encoders/video/hevc_videotoolbox.js | 26 +- src/misc/coders/Encoders/video/none.js | 17 +- src/misc/coders/Encoders/video/rawvideo.js | 17 +- src/misc/coders/Encoders/video/vp9_libvpx.js | 17 +- src/misc/coders/Encoders/video/vp9_vaapi.js | 35 +-- src/misc/coders/settings/Audio.js | 116 ++++--- src/misc/coders/settings/Video.js | 282 +++++++----------- src/misc/controls/HLS.js | 13 +- src/misc/controls/License/index.js | 31 +- src/misc/controls/Limits.js | 13 +- src/misc/controls/Metadata.js | 13 +- src/misc/controls/Preview.js | 15 +- src/misc/controls/Process.js | 13 +- src/misc/controls/RTMP.js | 20 +- src/misc/controls/SRT.js | 20 +- src/misc/controls/Snapshot.js | 13 +- src/misc/controls/Source.js | 30 +- src/misc/filters/audio/Loudnorm.js | 11 +- src/misc/filters/audio/Pan.js | 20 +- src/misc/filters/audio/Resample.js | 89 +++--- src/misc/filters/audio/Volume.js | 40 +-- src/misc/filters/video/Bwdif.js | 38 +-- src/misc/filters/video/Framerate.js | 39 +-- src/misc/filters/video/HFlip.js | 11 +- src/misc/filters/video/Scale.js | 20 +- src/misc/filters/video/Transpose.js | 20 +- src/misc/filters/video/VFlip.js | 11 +- src/misc/modals/Debug.js | 20 +- src/misc/modals/Dialog.js | 82 +++-- src/misc/modals/Hint.js | 81 ++--- src/misc/modals/Probe.js | 14 +- src/misc/modals/Process.js | 36 ++- src/misc/modals/Textarea.js | 27 +- src/theme/components/chip.js | 4 +- src/utils/auth0.js | 2 +- src/views/ChannelSelect.js | 8 +- src/views/Edit/Profile.js | 99 +++--- src/views/Edit/ProfileSummary.js | 19 +- src/views/Edit/SourceSelect.js | 84 +++--- src/views/Edit/Sources/ALSA.js | 29 +- src/views/Edit/Sources/AVFoundation.js | 31 +- src/views/Edit/Sources/AudioLoop.js | 31 +- src/views/Edit/Sources/Framebuffer.js | 26 +- src/views/Edit/Sources/Network.js | 206 ++++++------- src/views/Edit/Sources/NoAudio.js | 9 +- src/views/Edit/Sources/Raspicam.js | 19 +- src/views/Edit/Sources/V4L.js | 20 +- src/views/Edit/Sources/VideoAudio.js | 9 +- src/views/Edit/Sources/VideoLoop.js | 31 +- src/views/Edit/Sources/VirtualAudio.js | 14 +- src/views/Edit/Sources/VirtualVideo.js | 14 +- src/views/Edit/StreamSelect.js | 34 +-- src/views/Edit/Summary.js | 18 +- src/views/Edit/Wizard/Abort.js | 19 +- src/views/Edit/Wizard/Audio.js | 60 ++-- src/views/Edit/Wizard/Error.js | 12 +- src/views/Edit/Wizard/License.js | 19 +- src/views/Edit/Wizard/Metadata.js | 19 +- src/views/Edit/Wizard/Probe.js | 8 +- src/views/Edit/Wizard/Saving.js | 8 +- src/views/Edit/Wizard/Source.js | 15 +- src/views/Edit/Wizard/Sources/AVFoundation.js | 36 +-- src/views/Edit/Wizard/Sources/InternalHLS.js | 24 +- src/views/Edit/Wizard/Sources/InternalRTMP.js | 30 +- src/views/Edit/Wizard/Sources/InternalSRT.js | 28 +- src/views/Edit/Wizard/Sources/Network.js | 24 +- src/views/Edit/Wizard/Sources/Raspicam.js | 21 +- src/views/Edit/Wizard/Sources/V4L.js | 24 +- src/views/Edit/Wizard/Video.js | 37 ++- src/views/Edit/Wizard/VideoProfile.js | 64 ++-- src/views/Edit/Wizard/index.js | 50 ++-- src/views/Edit/index.js | 42 ++- src/views/Incompatible.js | 18 +- src/views/Invalid.js | 10 +- src/views/Login.js | 22 +- src/views/Main/Egress.js | 74 +++-- src/views/Main/Progress.js | 14 +- src/views/Main/Publication.js | 25 +- src/views/Main/index.js | 76 ++--- src/views/Password.js | 42 ++- src/views/Playersite.js | 104 ++----- src/views/Publication/Add.js | 16 +- src/views/Publication/Edit.js | 38 ++- src/views/Publication/Player.js | 30 +- src/views/Publication/Process.js | 12 +- src/views/Publication/Services/Akamai.js | 14 +- src/views/Publication/Services/Azure.js | 14 +- src/views/Publication/Services/Brightcove.js | 14 +- src/views/Publication/Services/CDN77.js | 14 +- src/views/Publication/Services/Core.js | 22 +- src/views/Publication/Services/DASH.js | 14 +- src/views/Publication/Services/DLive.js | 14 +- src/views/Publication/Services/DaCast.js | 14 +- src/views/Publication/Services/Dailymotion.js | 18 +- src/views/Publication/Services/Dummy.js | 18 +- src/views/Publication/Services/Facebook.js | 14 +- src/views/Publication/Services/Framebuffer.js | 16 +- src/views/Publication/Services/HLS.js | 14 +- src/views/Publication/Services/Icecast.js | 16 +- src/views/Publication/Services/Image2.js | 14 +- src/views/Publication/Services/Instagram.js | 29 +- src/views/Publication/Services/Kick.js | 14 +- src/views/Publication/Services/Linkedin.js | 14 +- src/views/Publication/Services/Livepush.js | 14 +- .../Publication/Services/Livespotting.js | 14 +- src/views/Publication/Services/MPEGTS.js | 14 +- .../Publication/Services/MediaNetwork.js | 14 +- src/views/Publication/Services/NimoTv.js | 14 +- src/views/Publication/Services/Owncast.js | 14 +- src/views/Publication/Services/PeerTube.js | 14 +- src/views/Publication/Services/PicartoTv.js | 14 +- src/views/Publication/Services/RTMP.js | 24 +- src/views/Publication/Services/RTSP.js | 14 +- src/views/Publication/Services/Red5.js | 14 +- src/views/Publication/Services/Restream.js | 14 +- src/views/Publication/Services/Rumble.js | 14 +- src/views/Publication/Services/SRT.js | 14 +- src/views/Publication/Services/Telegram.js | 14 +- src/views/Publication/Services/TikTok.js | 14 +- src/views/Publication/Services/Trovo.js | 14 +- src/views/Publication/Services/Twitch.js | 14 +- src/views/Publication/Services/Twitter.js | 16 +- src/views/Publication/Services/UDP.js | 14 +- src/views/Publication/Services/Vimeo.js | 14 +- src/views/Publication/Services/WOWZA.js | 14 +- src/views/Publication/Services/Wettercom.js | 16 +- src/views/Publication/Services/Youtube.js | 24 +- src/views/Publication/TabContent.js | 18 +- src/views/Settings.js | 47 ++- src/views/Welcome.js | 2 +- 211 files changed, 2221 insertions(+), 3654 deletions(-) diff --git a/src/Footer.js b/src/Footer.js index a6c645b..6597f70 100644 --- a/src/Footer.js +++ b/src/Footer.js @@ -67,7 +67,11 @@ const useStyles = makeStyles((theme) => ({ }, })); -function Resources(props) { +function Resources({ + getResources = () => { + return null; + }, +}) { const classes = useStyles(); const [$popover, setPopover] = React.useState(null); const [$resources, setResources] = React.useState(null); @@ -94,7 +98,7 @@ function Resources(props) { }, []); const update = async () => { - const resources = await props.resources(); + const resources = await getResources(); if (resources === null) { return; } @@ -347,12 +351,6 @@ function Resources(props) { ); } -Resources.defaultProps = { - resources: () => { - return null; - }, -}; - const initVersion = (initialVersion) => { if (!initialVersion) { initialVersion = {}; @@ -367,11 +365,18 @@ const initVersion = (initialVersion) => { return version; }; -export default function Footer(props) { +export default function Footer({ + expand = false, + app = '', + name = '', + version = initVersion(), + getResources = () => { + return null; + }, +}) { const classes = useStyles(); - const version = initVersion(props.version); - if (props.expand === true) { + if (expand === true) { return ( @@ -379,10 +384,10 @@ export default function Footer(props) { - {props.app} v{version.number} ({version.arch}) {props.name ? '- ' + props.name : ''} + {app} v{version.number} ({version.arch}) {name ? '- ' + name : ''} - + @@ -401,13 +406,3 @@ export default function Footer(props) { ); } } - -Footer.defaultProps = { - expand: false, - app: '', - name: '', - version: initVersion(), - resources: () => { - return null; - }, -}; diff --git a/src/Header.js b/src/Header.js index 0cdfffa..41e664d 100644 --- a/src/Header.js +++ b/src/Header.js @@ -160,12 +160,12 @@ const StyledMenu = styled((props) => ( }, })); -function AboutModal(props) { +function AboutModal({ open = false, onClose = () => {} }) { const classes = useStyles(); return ( - - + + @@ -215,12 +215,17 @@ function AboutModal(props) { ); } -AboutModal.defaultProps = { - open: false, - onClose: () => {}, -}; - -function HeaderMenu(props) { +function HeaderMenu({ + onChannel = () => {}, + onPlayersite = () => {}, + onSettings = () => {}, + onLogout = () => {}, + expand = true, + showPlayersite = false, + showSettings = false, + hasUpdates = false, + hasService = false, +}) { const classes = useStyles(); const [$anchorEl, setAnchorEl] = React.useState(null); @@ -238,17 +243,17 @@ function HeaderMenu(props) { Storage.Set('language', language); }; - if (props.expand === true) { + if (expand === true) { return ( - + - + - {props.hasService === true && ( + {hasService === true && ( @@ -259,18 +264,18 @@ function HeaderMenu(props) { )} - {props.showPlayersite === true && ( - + {showPlayersite === true && ( + Playersite )} - {props.showSettings === true && ( - + {showSettings === true && ( + - + System @@ -300,7 +305,7 @@ function HeaderMenu(props) { - + @@ -348,19 +353,17 @@ function HeaderMenu(props) { } } -HeaderMenu.defaultProps = { - onChannel: () => {}, - onPlayersite: () => {}, - onSettings: () => {}, - onLogout: () => {}, - expand: false, - showPlayersite: false, - showSettings: false, - hasUpdates: false, - hasService: false, -}; - -export default function Header(props) { +export default function Header({ + onChannel = () => {}, + onPlayersite = () => {}, + onSettings = () => {}, + onLogout = () => {}, + expand = true, + showPlayersite = false, + showSettings = false, + hasUpdates = false, + hasService = false, +}) { const classes = useStyles(); return ( @@ -372,14 +375,20 @@ export default function Header(props) { Restreamer - + ); } - -Header.defaultProps = { - expand: false, -}; diff --git a/src/RestreamerUI.js b/src/RestreamerUI.js index 7b22b16..6ac419d 100644 --- a/src/RestreamerUI.js +++ b/src/RestreamerUI.js @@ -40,7 +40,7 @@ const useStyles = makeStyles((theme) => ({ }, })); -export default function RestreamerUI(props) { +export default function RestreamerUI({ address = '' }) { const classes = useStyles(); const [$state, setState] = React.useState({ @@ -121,7 +121,7 @@ export default function RestreamerUI(props) { }; const handleMount = async () => { - restreamer.current = new Restreamer(props.address); + restreamer.current = new Restreamer(address); restreamer.current.AddListener((event) => { notify(event.severity, event.type, event.message); }); @@ -452,7 +452,7 @@ export default function RestreamerUI(props) { name = restreamer.current.Name(); } - let resources = () => { + let getResources = () => { return null; }; @@ -490,7 +490,7 @@ export default function RestreamerUI(props) { ); } else { view = ; - resources = handleResources; + getResources = handleResources; } } @@ -523,7 +523,7 @@ export default function RestreamerUI(props) { -