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: `
`,
+ 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() {
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',
];
diff --git a/src/utils/restreamer.js b/src/utils/restreamer.js
index 5f120a8..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) {
@@ -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"
+ />
+ } onClick={handleRefresh} sx={{ float: 'right' }}>
+ Refresh
+
+
+
+
+ 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);
};