Merge pull request #8 from datarhei/hls_versions
Add HLS version selection (Electra Player compatibility)
This commit is contained in:
commit
f23143c0ef
@ -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
|
||||
|
||||
@ -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",
|
||||
|
||||
@ -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: [],
|
||||
|
||||
@ -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"
|
||||
|
||||
@ -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);
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user