From aa168db7d5cf494f3fd195a7e1265f64d7faaac5 Mon Sep 17 00:00:00 2001 From: Ingo Oppermann Date: Fri, 29 Nov 2024 13:00:50 +0100 Subject: [PATCH 1/4] Add channel source --- src/utils/restreamer.js | 12 +++ src/views/Edit/Profile.js | 4 - src/views/Edit/Sources/Channel.js | 125 ++++++++++++++++++++++++++++++ src/views/Edit/Sources/X11grab.js | 2 +- src/views/Edit/Sources/index.js | 2 + src/views/Edit/index.js | 2 + 6 files changed, 142 insertions(+), 5 deletions(-) create mode 100644 src/views/Edit/Sources/Channel.js diff --git a/src/utils/restreamer.js b/src/utils/restreamer.js index 5f120a8..f8a785f 100644 --- a/src/utils/restreamer.js +++ b/src/utils/restreamer.js @@ -514,6 +514,9 @@ class Restreamer { virtualvideo: [], videoloop: [], audioloop: [], + channel: [], + noaudio: [], + sdp: [], }, sinks: {}, }; @@ -677,6 +680,15 @@ class Restreamer { skills.sources['network'].push(...channels); + channels = this.ListChannels().map((channel) => { + return { + id: channel.channelid, + name: channel.name, + }; + }); + + skills.sources['channel'].push(...channels); + this.skills = skills; } diff --git a/src/views/Edit/Profile.js b/src/views/Edit/Profile.js index 1f7d986..8c64abe 100644 --- a/src/views/Edit/Profile.js +++ b/src/views/Edit/Profile.js @@ -78,10 +78,6 @@ export default function Profile({ }, []); const load = async () => { - // Add pseudo sources - skills.sources.noaudio = []; - skills.sources.sdp = []; - let audio = $sources.audio; let hasAudio = false; diff --git a/src/views/Edit/Sources/Channel.js b/src/views/Edit/Sources/Channel.js new file mode 100644 index 0000000..f6996de --- /dev/null +++ b/src/views/Edit/Sources/Channel.js @@ -0,0 +1,125 @@ +import React from 'react'; + +import { useLingui } from '@lingui/react'; +import { Trans, t } from '@lingui/macro'; +import VideocamIcon from '@mui/icons-material/Videocam'; +import makeStyles from '@mui/styles/makeStyles'; +import Button from '@mui/material/Button'; +import Grid from '@mui/material/Grid'; +import RefreshIcon from '@mui/icons-material/Refresh'; +import Typography from '@mui/material/Typography'; + +import FormInlineButton from '../../../misc/FormInlineButton'; +import SelectCustom from '../../../misc/SelectCustom'; + +const useStyles = makeStyles((theme) => ({ + gridContainer: { + marginTop: '0.5em', + }, +})); + +const initSettings = (initialSettings) => { + if (!initialSettings) { + initialSettings = {}; + } + + const settings = { + channelid: 'none', + ...initialSettings, + }; + + return settings; +}; + +const createInputs = (settings) => { + const address = `{fs:mem}/${settings.channelid}.m3u8`; + const input = { + address: address, + options: [], + }; + + return [input]; +}; + +function Source({ knownDevices = [], settings = {}, onChange = function (settings) {}, onProbe = function (settings, inputs) {}, onRefresh = function () {} }) { + const classes = useStyles(); + const { i18n } = useLingui(); + settings = initSettings(settings); + + const handleChange = (what) => (event) => { + let data = {}; + + if (['channelid'].includes(what)) { + data[what] = event.target.value; + } + + onChange({ + ...settings, + ...data, + }); + }; + + const handleRefresh = () => { + onRefresh(); + }; + + const handleProbe = () => { + onProbe(settings, createInputs(settings)); + }; + + const options = knownDevices.map((device) => { + return { + value: device.id, + label: device.name + ' (' + device.id + ')', + }; + }); + + options.unshift({ + value: 'none', + label: i18n._(t`Choose an input channel ...`), + disabled: true, + }); + + return ( + + + + Select a channel: + + + + Video device} + value={settings.channelid} + onChange={handleChange('channelid')} + variant="outlined" + /> + + + + + Probe + + + + ); +} + +function SourceIcon(props) { + return ; +} + +const id = 'channel'; +const name = Channel; +const capabilities = ['audio', 'video']; +const ffversion = '^4.1.0 || ^5.0.0 || ^6.1.0 || ^7.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/X11grab.js b/src/views/Edit/Sources/X11grab.js index 508abc1..a861eb8 100644 --- a/src/views/Edit/Sources/X11grab.js +++ b/src/views/Edit/Sources/X11grab.js @@ -1,8 +1,8 @@ import React from 'react'; import { useLingui } from '@lingui/react'; -import ScreenshotMonitorIcon from '@mui/icons-material/ScreenshotMonitor'; import { Trans, t } from '@lingui/macro'; +import ScreenshotMonitorIcon from '@mui/icons-material/ScreenshotMonitor'; import makeStyles from '@mui/styles/makeStyles'; import Button from '@mui/material/Button'; import Grid from '@mui/material/Grid'; diff --git a/src/views/Edit/Sources/index.js b/src/views/Edit/Sources/index.js index cf56731..e8293a3 100644 --- a/src/views/Edit/Sources/index.js +++ b/src/views/Edit/Sources/index.js @@ -11,6 +11,7 @@ import * as VideoLoop from './VideoLoop'; import * as VirtualAudio from './VirtualAudio'; import * as VirtualVideo from './VirtualVideo'; import * as X11grab from './X11grab'; +import * as Channel from './Channel'; class Registry { constructor() { @@ -54,5 +55,6 @@ registry.Register(VideoLoop); registry.Register(AudioLoop); registry.Register(SDP); registry.Register(X11grab); +registry.Register(Channel); export default registry; diff --git a/src/views/Edit/index.js b/src/views/Edit/index.js index 2ab3773..0f39df5 100644 --- a/src/views/Edit/index.js +++ b/src/views/Edit/index.js @@ -112,6 +112,7 @@ export default function Edit({ restreamer = null }) { }); const skills = await restreamer.Skills(); + skills.sources['channel'] = skills.sources['channel'].filter((channel) => channel.id !== _channelid); setSkills(skills); const config = await restreamer.ConfigActive(); @@ -208,6 +209,7 @@ export default function Edit({ restreamer = null }) { await restreamer.RefreshSkills(); const skills = await restreamer.Skills(); + skills.sources['channel'] = skills.sources['channel'].filter((channel) => channel.id !== _channelid); setSkills(skills); }; From c04b924d9ee717186b97616135c374062443b372 Mon Sep 17 00:00:00 2001 From: Jan Stabenow Date: Wed, 4 Dec 2024 10:07:32 +0100 Subject: [PATCH 2/4] Fix videojs overlay --- public/_player/videojs/player.html | 45 +++++++++++------------------- 1 file changed, 16 insertions(+), 29 deletions(-) diff --git a/public/_player/videojs/player.html b/public/_player/videojs/player.html index a83610b..88d9df8 100644 --- a/public/_player/videojs/player.html +++ b/public/_player/videojs/player.html @@ -114,36 +114,23 @@ var player = videojs('player', config); if(playerConfig.logo.image.length != 0) { - var overlay = null; - - var imgTag = new Image(); - imgTag.onLoad = function () { - imgTag.setAttribute('width', this.width); - imgTag.setAttribute('height'.this.height); + const imgTag = new Image(); + imgTag.src = playerConfig.logo.image; + imgTag.onload = function () { + const logoWidth = imgTag.width; + const logoHeight = imgTag.height; + const overlayPosition = playerConfig.logo.position || 'top-left'; + const overlayLink = playerConfig.logo.link || '#'; + player.overlay({ + overlays: [{ + content: `Logo`, + start: 'play', + end: 'pause', + align: overlayPosition, + showBackground: false + }] + }); }; - imgTag.src = playerConfig.logo.image + '?' + Math.random(); - - if (playerConfig.logo.link.length !== 0) { - var aTag = document.createElement('a'); - aTag.setAttribute('href', playerConfig.logo.link); - aTag.setAttribute('target', '_blank'); - aTag.appendChild(imgTag); - overlay = aTag.outerHTML; - } else { - overlay = imgTag.outerHTML; - } - - player.overlay({ - align: playerConfig.logo.position, - overlays: [ - { - showBackground: false, - content: overlay, - start: 'play', - end: 'pause', - }, - ], - }); } player.ready(function() { From dd7adc39bf860b2faadba2ba86e5e65a2f9101b5 Mon Sep 17 00:00:00 2001 From: Ingo Oppermann Date: Wed, 22 Jan 2025 13:50:22 +0100 Subject: [PATCH 3/4] Move loading skills to a later point in time --- src/utils/restreamer.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/utils/restreamer.js b/src/utils/restreamer.js index f8a785f..a6f47e3 100644 --- a/src/utils/restreamer.js +++ b/src/utils/restreamer.js @@ -381,9 +381,9 @@ class Restreamer { return; } - await this._initSkills(); await this._initConfig(); await this._discoverChannels(); + await this._initSkills(); } _setTokenRefresh(expiresIn) { From 94a29ddcbe3546aae58da2694141a6de871a8598 Mon Sep 17 00:00:00 2001 From: Ingo Oppermann Date: Wed, 22 Jan 2025 13:51:10 +0100 Subject: [PATCH 4/4] Force IDR key frames --- src/misc/coders/Encoders/video/hevc_libx265.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/misc/coders/Encoders/video/hevc_libx265.js b/src/misc/coders/Encoders/video/hevc_libx265.js index 951a164..3e808df 100644 --- a/src/misc/coders/Encoders/video/hevc_libx265.js +++ b/src/misc/coders/Encoders/video/hevc_libx265.js @@ -43,6 +43,8 @@ function createMapping(settings, stream, skills) { `${settings.fps}`, '-sc_threshold', '0', + '-forced-idr', + '1', '-pix_fmt', 'yuv420p', ];