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);