Merge pull request #8 from datarhei/hls_versions

Add HLS version selection (Electra Player compatibility)
This commit is contained in:
Jan Stabenow 2022-07-01 14:24:09 +02:00 committed by GitHub
commit f23143c0ef
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 136 additions and 67 deletions

View File

@ -1,10 +1,12 @@
# Restreamer-UI
#### v1.1.0 > v1.4.0
#### v1.1.0 > v1.5.0
- Add HLS version selection (Dwaynarang, Electra Player compatibility)
- Add Owncast to publication services ([#369](https://github.com/datarhei/restreamer/issues/369))
- Add Telegram to publication services (thx Martin Held)
- Add Polish translations (thx Robert Rykała)
- Mod Allow decoders and encoders to set global options
- Fix process report naming
- Fix publication service icon styles
- Fix VAAPI encoder

View File

@ -1,6 +1,6 @@
{
"name": "restreamer-ui",
"version": "1.4.0",
"version": "1.5.0",
"bundle": "restreamer-v2.x.x",
"private": false,
"license": "Apache-2.0",

View File

@ -2,11 +2,10 @@ import React from 'react';
function createMapping(settings, stream) {
const local = ['-codec:a', 'copy'];
/*
if(stream.codec === 'aac') {
local.push('-bsf:a', 'aac_adtstoasc');
}
*/
//if (stream.codec === 'aac') {
// local.push('-bsf:a', 'aac_adtstoasc');
//}
const mapping = {
global: [],

View File

@ -2,10 +2,12 @@ import React from 'react';
import { Trans } from '@lingui/macro';
import Grid from '@mui/material/Grid';
import MenuItem from '@mui/material/MenuItem';
import TextField from '@mui/material/TextField';
import Typography from '@mui/material/Typography';
import Checkbox from '../Checkbox';
import Select from '../Select';
function init(settings) {
const initSettings = {
@ -13,6 +15,7 @@ function init(settings) {
segmentDuration: 2,
listSize: 6,
cleanup: true,
version: 3,
...settings,
};
@ -55,6 +58,20 @@ export default function Control(props) {
</Typography>
</Grid>
*/}
<Grid item xs={12}>
<Select label={<Trans>EXT-X-VERSION</Trans>} value={settings.version} onChange={handleChange('version')}>
<MenuItem value={3}>3</MenuItem>
<MenuItem value={6}>
<Trans>6 (+ guaranteed to start with a Key frame)</Trans>
</MenuItem>
<MenuItem value={7}>
<Trans>7 (+ fragmented MP4 format)</Trans>
</MenuItem>
</Select>
<Typography variant="caption">
<Trans>M3U8 manifest version. Version 3 has the best browser/client compatibility.</Trans>
</Typography>
</Grid>
<Grid item xs={12} md={6}>
<TextField
variant="outlined"

View File

@ -1462,14 +1462,14 @@ class Restreamer {
options: ['-dn', '-sn', ...outputs[0].options.map((o) => '' + o)],
cleanup: [
{
pattern: `memfs:/${channel.channelid}_*.ts`,
max_files: parseInt(control.hls.listSize) + 2,
max_file_age_seconds: control.hls.cleanup ? parseInt(control.hls.segmentDuration) * (parseInt(control.hls.listSize) + 2) : 0,
pattern: control.hls.version >= 7 ? `memfs:/${channel.channelid}_*.mp4` : `memfs:/${channel.channelid}_*.ts`,
max_files: parseInt(control.hls.listSize) + 6,
max_file_age_seconds: control.hls.cleanup ? parseInt(control.hls.segmentDuration) * (parseInt(control.hls.listSize) + 6) : 0,
purge_on_delete: true,
},
{
pattern: `memfs:/${channel.channelid}.m3u8`,
max_file_age_seconds: control.hls.cleanup ? parseInt(control.hls.segmentDuration) * (parseInt(control.hls.listSize) + 2) : 0,
max_file_age_seconds: control.hls.cleanup ? parseInt(control.hls.segmentDuration) * (parseInt(control.hls.listSize) + 6) : 0,
purge_on_delete: true,
},
],
@ -1481,63 +1481,114 @@ class Restreamer {
output.options.push(...metadata_options);
if (control.hls.lhls === false) {
// ordinary HLS
output.options.push(
'-f',
'hls',
'-start_number',
'0',
'-hls_time',
'' + parseInt(control.hls.segmentDuration),
'-hls_list_size',
'' + parseInt(control.hls.listSize),
'-hls_flags',
'append_list+delete_segments',
'-hls_segment_filename',
`{memfs}/${channel.channelid}_%04d.ts`,
'-y',
'-method',
'PUT'
);
// Manifest versions
// https://developer.apple.com/documentation/http_live_streaming/about_the_ext-x-version_tag
// https://ffmpeg.org/ffmpeg-all.html#Options-53
// Returns the raw l/hls parameters for an EXT-X-VERSION
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'],
['method', 'PUT'],
];
} 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'],
['hls_delete_threshold', '4'],
['hls_segment_filename', `{memfs}/${channel.channelid}_%04d.ts`],
['segment_format_options', 'mpegts_flags=mpegts_copyts=1'],
['max_muxing_queue_size', '400'],
['method', 'PUT'],
];
case 7:
// fix Malformed AAC bitstream detected for hls version 7
if (control.hls.version === 7 && output.options.includes('-codec:a') && output.options.includes('copy')) {
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'],
['hls_delete_threshold', '4'],
['hls_segment_type', 'fmp4'],
['hls_fmp4_init_filename', `${channel.channelid}_init.mp4`],
['hls_segment_filename', `{memfs}/${channel.channelid}_%04d.mp4`],
['segment_format_options', 'mpegts_flags=mpegts_copyts=1'],
['max_muxing_queue_size', '400'],
['method', 'PUT'],
];
// 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'],
['hls_delete_threshold', '4'],
['hls_segment_filename', `{memfs}/${channel.channelid}_%04d.ts`],
['segment_format_options', 'mpegts_flags=mpegts_copyts=1'],
['max_muxing_queue_size', '400'],
['method', 'PUT'],
];
}
}
};
const hls_params_raw = getHLSParams(control.hls.lhls, control.hls.version);
// 'tee_muxer' is required for the delivery of one output to multiple endpoints without processing the input for each output
// http://ffmpeg.org/ffmpeg-all.html#tee-1
const tee_muxer = false;
// Returns the l/hls parameters with or without tee_muxer
if (tee_muxer) {
// f=hls:start_number=0...
const hls_params = hls_params_raw
.filter((o) => {
if (o[0] === 'segment_format_options' || o[0] === 'max_muxing_queue_size') {
return false;
}
return true;
})
.map((o) => o[0] + '=' + o[1])
.join(':');
output.options.push('-tag:v', '7', '-tag:a', '10', '-f', 'tee');
// WARN: It is a magic function. Returns 'Invalid process config' and the process.id is lost (Core v16.8.0) <= this is not the case anymore with the latest dev branch
// ['f=hls:start_number=0...]address.m3u8
output.address = `[` + hls_params + `]{memfs}/${channel.channelid}.m3u8`;
} else {
// Low Latency HLS
output.address = `{memfs}/${channel.channelid}.mpd`;
output.options.push(
'-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',
'-y',
'-method',
'PUT'
);
// ['-f', 'hls', '-start_number', '0', ...]
// adding the '-' in front of the first option, then flatten everything
const hls_params = hls_params_raw.map((o) => ['-' + o[0], o[1]]).reduce((acc, val) => acc.concat(val), []);
output.options.push(...hls_params);
}
proc.output.push(output);