Allow to stream HEVC and AV1 to Youtube via RTMP
This commit is contained in:
parent
82bd4f2d76
commit
6292e62858
@ -23,6 +23,8 @@ function InitSkills(initialSkills) {
|
||||
|
||||
skills.ffmpeg = {
|
||||
version: '5.0.0',
|
||||
version_major: 5,
|
||||
version_minor: 0,
|
||||
...skills.ffmpeg,
|
||||
};
|
||||
|
||||
|
||||
@ -46,7 +46,7 @@ export default function Control(props) {
|
||||
onChange={handleChange('cpu_usage')}
|
||||
/>
|
||||
<Typography variant="caption">
|
||||
<Trans>CPU usage limit in percent (0-100%), 0 for unlimited</Trans>
|
||||
<Trans>CPU usage limit in percent (0-100%), 0 for unlimited.</Trans>
|
||||
</Typography>
|
||||
</Grid>
|
||||
<Grid item xs={12} md={4}>
|
||||
|
||||
@ -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: [],
|
||||
|
||||
@ -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',
|
||||
)}
|
||||
/>
|
||||
<Typography variant="caption">
|
||||
|
||||
@ -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 = (
|
||||
<Trans>
|
||||
@ -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') {
|
||||
|
||||
@ -193,6 +193,9 @@ export function conflateServiceSkills(requires, skills) {
|
||||
requires = validateRequirements(requires);
|
||||
|
||||
const serviceSkills = {
|
||||
ffmpeg: {
|
||||
...skills.ffmpeg,
|
||||
},
|
||||
protocols: [],
|
||||
formats: [],
|
||||
devices: {},
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user