diff --git a/src/misc/coders/helper/index.js b/src/misc/coders/helper/index.js
index 47213e9..9bf5c1b 100644
--- a/src/misc/coders/helper/index.js
+++ b/src/misc/coders/helper/index.js
@@ -23,6 +23,8 @@ function InitSkills(initialSkills) {
skills.ffmpeg = {
version: '5.0.0',
+ version_major: 5,
+ version_minor: 0,
...skills.ffmpeg,
};
diff --git a/src/misc/controls/Limits.js b/src/misc/controls/Limits.js
index 16a71d5..3e762c7 100644
--- a/src/misc/controls/Limits.js
+++ b/src/misc/controls/Limits.js
@@ -46,7 +46,7 @@ export default function Control(props) {
onChange={handleChange('cpu_usage')}
/>
- CPU usage limit in percent (0-100%), 0 for unlimited
+ CPU usage limit in percent (0-100%), 0 for unlimited.
diff --git a/src/utils/restreamer.js b/src/utils/restreamer.js
index d64d81c..2344b9a 100644
--- a/src/utils/restreamer.js
+++ b/src/utils/restreamer.js
@@ -6,6 +6,8 @@ import Handlebars from 'handlebars/dist/cjs/handlebars';
import SemverSatisfies from 'semver/functions/satisfies';
import SemverGt from 'semver/functions/gt';
import SemverGte from 'semver/functions/gte';
+import SemverMajor from 'semver/functions/major';
+import SemverMinor from 'semver/functions/minor';
import * as M from './metadata';
import * as Storage from './storage';
@@ -478,6 +480,8 @@ class Restreamer {
const skills = {
ffmpeg: {
version: '',
+ version_major: 0,
+ version_minor: 0,
},
codecs: {
audio: {
@@ -536,6 +540,9 @@ class Restreamer {
...val.ffmpeg,
};
+ skills.ffmpeg.version_major = SemverMajor(skills.ffmpeg.version);
+ skills.ffmpeg.version_minor = SemverMinor(skills.ffmpeg.version);
+
val.codecs = {
audio: {},
video: {},
@@ -2616,12 +2623,32 @@ class Restreamer {
// from the inputs only the first is used and only its options are considered.
let address = '';
+ let options = [];
if (control.source.source === 'hls+memfs') {
address = `{memfs}/${channel.channelid}.m3u8`;
+ options.push('-re');
} else if (control.source.source === 'hls+diskfs') {
address = `{diskfs}/${channel.channelid}.m3u8`;
+ options.push('-re');
} else if (control.source.source === 'rtmp') {
address = `{rtmp,name=${channel.channelid}.stream}`;
+ const skills = this.Skills();
+ if (skills.ffmpeg.version_major >= 6) {
+ const codecs = [];
+ if (skills.codecs.video.hevc?.length > 0) {
+ codecs.push('hvc1');
+ }
+ if (skills.codecs.video.av1?.length > 0) {
+ codecs.push('av01');
+ }
+ if (skills.codecs.video.vp9?.length > 0) {
+ codecs.push('vp09');
+ }
+
+ if (codecs.length !== 0) {
+ options.push('-rtmp_enhanced_codecs', codecs.join(','));
+ }
+ }
} else if (control.source.source === 'srt') {
address = `{srt,name=${channel.channelid},mode=request}`;
}
@@ -2634,7 +2661,7 @@ class Restreamer {
{
id: 'input_0',
address: address,
- options: ['-re', ...inputs[0].options],
+ options: [...options, ...inputs[0].options],
},
],
output: [],
diff --git a/src/views/Edit/Sources/Network.js b/src/views/Edit/Sources/Network.js
index 44bc68f..4fa2167 100644
--- a/src/views/Edit/Sources/Network.js
+++ b/src/views/Edit/Sources/Network.js
@@ -1,6 +1,5 @@
import React from 'react';
import { useNavigate } from 'react-router-dom';
-import SemverSatisfies from 'semver/functions/satisfies';
import { useLingui } from '@lingui/react';
import { Trans, t } from '@lingui/macro';
@@ -149,6 +148,8 @@ const initSkills = (initialSkills) => {
skills.ffmpeg = {
version: '0.0.0',
+ version_major: 0,
+ version_minor: 0,
...skills.ffmpeg,
};
@@ -182,13 +183,6 @@ const createInputs = (settings, config, skills) => {
settings = initSettings(settings, config);
skills = initSkills(skills);
- let ffmpeg_version = 6;
- if (SemverSatisfies(skills.ffmpeg.version, '^5.0.0')) {
- ffmpeg_version = 5;
- } else if (SemverSatisfies(skills.ffmpeg.version, '^4.1.0')) {
- ffmpeg_version = 4;
- }
-
const input = {
address: '',
options: [],
@@ -241,7 +235,7 @@ const createInputs = (settings, config, skills) => {
if (settings.general.use_wallclock_as_timestamps) {
input.options.push('-use_wallclock_as_timestamps', '1');
}
- if (ffmpeg_version === 5 && settings.general.avoid_negative_ts !== 'auto') {
+ if (skills.ffmpeg.version_major >= 5 && settings.general.avoid_negative_ts !== 'auto') {
input.options.push('-avoid_negative_ts', settings.general.avoid_negative_ts);
}
@@ -259,7 +253,7 @@ const createInputs = (settings, config, skills) => {
input.options.push('-analyzeduration', settings.general.analyzeduration_rtmp);
}
- if (ffmpeg_version === 6) {
+ if (skills.ffmpeg.version_major >= 6) {
const codecs = [];
if (skills.codecs.video.hevc?.length > 0) {
codecs.push('hvc1');
@@ -290,7 +284,7 @@ const createInputs = (settings, config, skills) => {
input.options.push('-analyzeduration', settings.general.analyzeduration_rtmp);
}
- if (ffmpeg_version === 6) {
+ if (skills.ffmpeg.version_major >= 6) {
const codecs = [];
if (skills.codecs.video.hevc?.length > 0) {
codecs.push('hvc1');
@@ -317,7 +311,7 @@ const createInputs = (settings, config, skills) => {
input.address = addUsernamePassword(input.address, settings.username, settings.password);
if (protocol === 'rtsp') {
- if (ffmpeg_version === 4) {
+ if (skills.ffmpeg.version_major === 4) {
input.options.push('-stimeout', settings.rtsp.stimeout);
} else {
input.options.push('-timeout', settings.rtsp.stimeout);
@@ -461,7 +455,7 @@ const getRTMPAddress = (host, app, name, token, secure) => {
let url = 'rtmp' + (secure ? 's' : '') + '://' + host + app + '/' + name + '.stream';
if (token.length !== 0) {
- url += '?token=' + encodeURIComponent(token);
+ url += '/token=' + encodeURIComponent(token);
}
return url;
@@ -689,13 +683,13 @@ function AdvancedSettings(props) {
? settings.push.type === 'hls'
? settings.general.analyzeduration_http
: settings.push.type === 'rtmp'
- ? settings.general.analyzeduration_rtmp
- : settings.general.analyzeduration
+ ? settings.general.analyzeduration_rtmp
+ : settings.general.analyzeduration
: protocolClass === 'http'
- ? settings.general.analyzeduration_http
- : protocolClass === 'rtmp'
- ? settings.general.analyzeduration_rtmp
- : settings.general.analyzeduration
+ ? settings.general.analyzeduration_http
+ : protocolClass === 'rtmp'
+ ? settings.general.analyzeduration_rtmp
+ : settings.general.analyzeduration
}
onChange={props.onChange(
'general',
@@ -703,13 +697,13 @@ function AdvancedSettings(props) {
? settings.push.type === 'hls'
? 'analyzeduration_http'
: settings.push.type === 'rtmp'
- ? 'analyzeduration_rtmp'
- : 'analyzeduration'
+ ? 'analyzeduration_rtmp'
+ : 'analyzeduration'
: protocolClass === 'http'
- ? 'analyzeduration_http'
- : protocolClass === 'rtmp'
- ? 'analyzeduration_rtmp'
- : 'analyzeduration',
+ ? 'analyzeduration_http'
+ : protocolClass === 'rtmp'
+ ? 'analyzeduration_rtmp'
+ : 'analyzeduration',
)}
/>
diff --git a/src/views/Publication/Services/Youtube.js b/src/views/Publication/Services/Youtube.js
index 0977a7a..478e9f6 100644
--- a/src/views/Publication/Services/Youtube.js
+++ b/src/views/Publication/Services/Youtube.js
@@ -15,7 +15,7 @@ import Select from '../../../misc/Select';
const id = 'youtube';
const name = 'YouTube Live';
-const version = '1.0';
+const version = '1.1';
const stream_key_link = 'https://www.youtube.com/live_dashboard';
const description = (
@@ -54,8 +54,7 @@ const requires = {
formats: ['flv', 'hls'],
codecs: {
audio: ['aac', 'mp3'],
- video: ['h264'],
- // video: ['h264', 'h265', 'vp9' , 'av1'],
+ video: ['h264', 'hevc', 'av1'],
},
};
@@ -100,18 +99,36 @@ function Service(props) {
}
if (settings.mode === 'rtmps') {
+ let options = ['-f', 'flv'];
+
+ console.log('codecs', props.skills.codecs);
+
+ if (props.skills.ffmpeg.version_major >= 6) {
+ const codecs = [];
+ if (props.skills.codecs.video.includes('hevc')) {
+ codecs.push('hvc1');
+ }
+ if (props.skills.codecs.video.includes('av1')) {
+ codecs.push('av01');
+ }
+
+ if (codecs.length !== 0) {
+ options.push('-rtmp_enhanced_codecs', codecs.join(','));
+ }
+ }
+
// https://developers.google.com/youtube/v3/live/guides/rtmps-ingestion
if (settings.primary === true) {
outputs.push({
address: 'rtmps://a.rtmp.youtube.com/live2/' + settings.stream_key,
- options: ['-f', 'flv'],
+ options: options.slice(),
});
}
if (settings.backup === true) {
outputs.push({
address: 'rtmps://b.rtmp.youtube.com/live2?backup=1/' + settings.stream_key,
- options: ['-f', 'flv'],
+ options: options.slice(),
});
}
} else if (settings.mode === 'hls') {
diff --git a/src/views/Publication/helper.js b/src/views/Publication/helper.js
index 88e4e4c..a8c1be8 100644
--- a/src/views/Publication/helper.js
+++ b/src/views/Publication/helper.js
@@ -193,6 +193,9 @@ export function conflateServiceSkills(requires, skills) {
requires = validateRequirements(requires);
const serviceSkills = {
+ ffmpeg: {
+ ...skills.ffmpeg,
+ },
protocols: [],
formats: [],
devices: {},