diff --git a/CHANGELOG.md b/CHANGELOG.md
index 9d0af8b..4533846 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -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
diff --git a/package.json b/package.json
index a454a81..7f8b30d 100644
--- a/package.json
+++ b/package.json
@@ -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",
diff --git a/src/misc/coders/Encoders/audio/Copy.js b/src/misc/coders/Encoders/audio/Copy.js
index d6f90cf..9065c38 100644
--- a/src/misc/coders/Encoders/audio/Copy.js
+++ b/src/misc/coders/Encoders/audio/Copy.js
@@ -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: [],
diff --git a/src/misc/controls/HLS.js b/src/misc/controls/HLS.js
index 6658cb8..03eb0b4 100644
--- a/src/misc/controls/HLS.js
+++ b/src/misc/controls/HLS.js
@@ -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) {
*/}
+
+
+
+ M3U8 manifest version. Version 3 has the best browser/client compatibility.
+
+
'' + 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);