lower gop size and keyframe interval (#2832)

* lower gop size and keyframe interval

* update changelog

* fix build using latest dotnet sdk

* fixes
This commit is contained in:
Jason Dove
2026-02-19 13:35:27 -06:00
committed by GitHub
parent a91de68a5c
commit 08cbf59527
22 changed files with 45 additions and 45 deletions

View File

@@ -57,7 +57,7 @@ jobs:
- name: Setup dotnet
uses: actions/setup-dotnet@v4
with:
dotnet-version: '10.0.102'
dotnet-version: '10.0.x'
- name: Clean
run: dotnet clean --configuration Release && dotnet nuget locals all --clear
@@ -172,7 +172,7 @@ jobs:
- name: Setup dotnet
uses: actions/setup-dotnet@v4
with:
dotnet-version: '10.0.102'
dotnet-version: '10.0.x'
- name: Clean
run: dotnet clean --configuration Release && dotnet nuget locals all --clear
@@ -229,7 +229,7 @@ jobs:
- name: Setup dotnet
uses: actions/setup-dotnet@v4
with:
dotnet-version: '10.0.102'
dotnet-version: '10.0.x'
- name: Clean
run: dotnet clean --configuration Release && dotnet nuget locals all --clear

View File

@@ -14,7 +14,7 @@ jobs:
- name: Setup dotnet
uses: actions/setup-dotnet@v4
with:
dotnet-version: '10.0.102'
dotnet-version: '10.0.x'
- name: Clean
run: dotnet clean --configuration Release && dotnet nuget locals all --clear
@@ -36,7 +36,7 @@ jobs:
- name: Setup dotnet
uses: actions/setup-dotnet@v4
with:
dotnet-version: '10.0.102'
dotnet-version: '10.0.x'
- name: Clean
run: dotnet clean --configuration Release && dotnet nuget locals all --clear
@@ -72,7 +72,7 @@ jobs:
- name: Setup dotnet
uses: actions/setup-dotnet@v4
with:
dotnet-version: '10.0.102'
dotnet-version: '10.0.x'
- name: Clean
run: dotnet clean --configuration Release && dotnet nuget locals all --clear
@@ -100,7 +100,7 @@ jobs:
- name: Setup dotnet
uses: actions/setup-dotnet@v4
with:
dotnet-version: '10.0.102'
dotnet-version: '10.0.x'
- name: Clean
run: dotnet clean --configuration Release && dotnet nuget locals all --clear

View File

@@ -20,11 +20,13 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
- If this feature turns out to be popular, methods to correct the drift may be investigated
- Add `ETV_INSTANCE_ID` environment variable to disambiguate EPG data from multiple ErsatzTV instances
- When set, the value will be used in channel identifiers before the final `.ersatztv.org`
- Show warning message when selecting audio format `aac (latm)` for general streaming use when it is only intended for DVB-C
### Changed
- Move dark/light mode toggle to **Settings** > **UI**
- Use latest (non-deprecated) authorization method with Jellyfin API
- Replace direct Discord links with new contact page https://ersatztv.org/contact which also includes other options like Matrix
- Lower GOP size and keyframe interval from four seconds to two seconds in accordance with HLS2 draft spec recommendations
### Fixed
- Improve stability of playback orders `Shuffle` and `Shuffle in Order` over time

View File

@@ -2,7 +2,7 @@
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<NoWarn>VSTHRD200</NoWarn>
<NoWarn>VSTHRD200,CA1873</NoWarn>
<ImplicitUsings>enable</ImplicitUsings>
<AnalysisLevel>latest-Recommended</AnalysisLevel>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>

View File

@@ -498,7 +498,7 @@ public class GetPlayoutItemProcessByChannelNumberHandler : FFmpegProcessHandler<
Option<TimeSpan> maybeDuration = maybeNextStart.Map(s => s - now);
// limit working ahead on errors to 1 minute
if (!request.HlsRealtime && maybeDuration.IfNone(TimeSpan.FromMinutes(2)) > TimeSpan.FromMinutes(1))
if (!request.HlsRealtime && await maybeDuration.IfNoneAsync(TimeSpan.FromMinutes(2)) > TimeSpan.FromMinutes(1))
{
maybeNextStart = now.AddMinutes(1);
maybeDuration = TimeSpan.FromMinutes(1);
@@ -766,7 +766,7 @@ public class GetPlayoutItemProcessByChannelNumberHandler : FFmpegProcessHandler<
.ToListAsync(cancellationToken);
// always play min(duration to next item, version.Duration)
TimeSpan duration = maybeDuration.IfNone(version.Duration);
TimeSpan duration = await maybeDuration.IfNoneAsync(version.Duration);
if (version.Duration < duration)
{
duration = version.Duration;

View File

@@ -2,7 +2,7 @@
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<NoWarn>VSTHRD200</NoWarn>
<NoWarn>VSTHRD200,CA1873</NoWarn>
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>

View File

@@ -2,7 +2,7 @@
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<NoWarn>VSTHRD200</NoWarn>
<NoWarn>VSTHRD200,CA1873</NoWarn>
<ImplicitUsings>enable</ImplicitUsings>
<AnalysisLevel>latest-Recommended</AnalysisLevel>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>

View File

@@ -893,7 +893,7 @@ public class FFmpegLibraryProcessService : IFFmpegProcessService
playbackSettings.NormalizeColors,
playbackSettings.Deinterlace);
var frameRate = playbackSettings.FrameRate.IfNone(new FrameRate("24"));
var frameRate = await playbackSettings.FrameRate.IfNoneAsync(new FrameRate("24"));
var ffmpegVideoStream = new VideoStream(
0,

View File

@@ -7,7 +7,7 @@
<AnalysisLevel>latest-Recommended</AnalysisLevel>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<NoWarn>VSTHRD200</NoWarn>
<NoWarn>VSTHRD200,CA1873</NoWarn>
</PropertyGroup>
<ItemGroup>

View File

@@ -32,8 +32,8 @@ public class OutputFormatConcatHls : IPipelineStep
return
[
"-g", $"{OutputFormatHls.SegmentSeconds}/2",
"-force_key_frames", $"expr:gte(t,n_forced*{OutputFormatHls.SegmentSeconds}/2)",
"-g", $"{OutputFormatHls.KeyframeIntervalSeconds}",
"-force_key_frames", $"expr:gte(t,n_forced*{OutputFormatHls.KeyframeIntervalSeconds})",
"-f", "hls",
"-hls_segment_type", segmentType,
//"-hls_init_time", "2",

View File

@@ -6,6 +6,7 @@ namespace ErsatzTV.FFmpeg.OutputFormat;
public class OutputFormatHls : IPipelineStep
{
public const int SegmentSeconds = 4;
public const int KeyframeIntervalSeconds = 2;
private readonly FrameState _desiredState;
private readonly bool _isFirstTranscode;
@@ -55,7 +56,7 @@ public class OutputFormatHls : IPipelineStep
int gop = _oneSecondGop
? (int)Math.Round(frameRate.ParsedFrameRate)
: (int)Math.Round(frameRate.ParsedFrameRate * SegmentSeconds);
: (int)Math.Round(frameRate.ParsedFrameRate * KeyframeIntervalSeconds);
List<string> result = [];
@@ -64,8 +65,8 @@ public class OutputFormatHls : IPipelineStep
result.AddRange(
[
"-g", $"{gop}",
"-keyint_min", $"{(int)Math.Round(frameRate.ParsedFrameRate * SegmentSeconds)}",
"-force_key_frames", $"expr:gte(t,n_forced*{SegmentSeconds})"
"-keyint_min", $"{(int)Math.Round(frameRate.ParsedFrameRate * KeyframeIntervalSeconds)}",
"-force_key_frames", $"expr:gte(t,n_forced*{KeyframeIntervalSeconds})"
]);
}

View File

@@ -4,7 +4,7 @@
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<NoWarn>VSTHRD200</NoWarn>
<NoWarn>VSTHRD200,CA1873</NoWarn>
<IsPackable>false</IsPackable>
</PropertyGroup>

View File

@@ -48,25 +48,22 @@ public static class DbInitializer
if (resource != null)
{
using var reader = new StreamReader(resource);
while (!reader.EndOfStream)
string line;
while ((line = await reader.ReadLineAsync(cancellationToken)) is not null)
{
string line = await reader.ReadLineAsync(cancellationToken);
if (line != null)
string[] split = line.Split("|");
if (split.Length == 5)
{
string[] split = line.Split("|");
if (split.Length == 5)
var languageCode = new LanguageCode
{
var languageCode = new LanguageCode
{
ThreeCode1 = split[0],
ThreeCode2 = split[1],
TwoCode = split[2],
EnglishName = split[3],
FrenchName = split[4]
};
ThreeCode1 = split[0],
ThreeCode2 = split[1],
TwoCode = split[2],
EnglishName = split[3],
FrenchName = split[4]
};
await context.LanguageCodes.AddAsync(languageCode, cancellationToken);
}
await context.LanguageCodes.AddAsync(languageCode, cancellationToken);
}
}
}

View File

@@ -3,7 +3,7 @@
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<GenerateRuntimeConfigurationFiles>true</GenerateRuntimeConfigurationFiles>
<NoWarn>VSTHRD200</NoWarn>
<NoWarn>VSTHRD200,CA1873</NoWarn>
<ImplicitUsings>enable</ImplicitUsings>
<AnalysisLevel>latest-Recommended</AnalysisLevel>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>

View File

@@ -21,9 +21,9 @@ public class LanguageCodeCache : ILanguageCodeCache
if (resource != null)
{
using var reader = new StreamReader(resource);
while (!reader.EndOfStream)
string line;
while ((line = await reader.ReadLineAsync(cancellationToken)) is not null)
{
string line = await reader.ReadLineAsync(cancellationToken);
if (string.IsNullOrWhiteSpace(line))
{
continue;

View File

@@ -4,7 +4,7 @@
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<NoWarn>VSTHRD200,CA1873</NoWarn>
<IsPackable>false</IsPackable>
</PropertyGroup>

View File

@@ -10,6 +10,7 @@
<UserSecretsId>729e6271-c307-43c8-8e36-1b36c39f6de2</UserSecretsId>
<AnalysisLevel>latest-Recommended</AnalysisLevel>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<NoWarn>VSTHRD200,CA1873</NoWarn>
</PropertyGroup>
<ItemGroup>

View File

@@ -6,7 +6,7 @@
<TypeScriptToolsVersion>Latest</TypeScriptToolsVersion>
<IsPackable>false</IsPackable>
<ImplicitUsings>enable</ImplicitUsings>
<NoWarn>VSTHRD200</NoWarn>
<NoWarn>VSTHRD200,CA1873</NoWarn>
<IncludeAllContentForSelfExtract>true</IncludeAllContentForSelfExtract>
<Configurations>Debug;Release;Debug No Sync</Configurations>
<Platforms>AnyCPU</Platforms>

View File

@@ -282,8 +282,7 @@
<div class="d-flex">
<MudText>Format</MudText>
</div>
<MudSelect @bind-Value="_model.AudioFormat" For="@(() => _model.AudioFormat)">
<MudSelectItem Value="@FFmpegProfileAudioFormat.Aac">aac</MudSelectItem>
<MudSelect @bind-Value="_model.AudioFormat" For="@(() => _model.AudioFormat)" HelperText="@(_model.AudioFormat is FFmpegProfileAudioFormat.AacLatm ? "aac (latm) is ONLY intended for DVB-C; anything else should use aac instead which uses ADTS" : string.Empty)"> <MudSelectItem Value="@FFmpegProfileAudioFormat.Aac">aac</MudSelectItem>
<MudSelectItem Value="@FFmpegProfileAudioFormat.Ac3">ac3</MudSelectItem>
<MudSelectItem Value="@FFmpegProfileAudioFormat.AacLatm">aac (latm)</MudSelectItem>
</MudSelect>

View File

@@ -9,7 +9,7 @@ RUN apt-get update && \
rm -rf /var/lib/apt/lists/*
# https://hub.docker.com/_/microsoft-dotnet
FROM mcr.microsoft.com/dotnet/sdk:10.0.102-noble-amd64 AS build
FROM mcr.microsoft.com/dotnet/sdk:10.0-noble-amd64 AS build
RUN apt-get update && apt-get install -y ca-certificates gnupg default-jre-headless python3-pip
WORKDIR /source

View File

@@ -4,7 +4,7 @@ FROM --platform=linux/arm/v7 ghcr.io/ersatztv/ersatztv-ffmpeg:7.1.1 AS runtime-b
COPY --from=dotnet-runtime /usr/share/dotnet /usr/share/dotnet
# https://hub.docker.com/_/microsoft-dotnet
FROM mcr.microsoft.com/dotnet/sdk:10.0.102-noble-amd64 AS build
FROM mcr.microsoft.com/dotnet/sdk:10.0-noble-amd64 AS build
RUN apt-get update && apt-get install -y ca-certificates gnupg
WORKDIR /source

View File

@@ -4,7 +4,7 @@ FROM --platform=linux/arm64 ghcr.io/ersatztv/ersatztv-ffmpeg:7.1.1 AS runtime-ba
COPY --from=dotnet-runtime /usr/share/dotnet /usr/share/dotnet
# https://hub.docker.com/_/microsoft-dotnet
FROM mcr.microsoft.com/dotnet/sdk:10.0.102-noble-arm64v8 AS build
FROM mcr.microsoft.com/dotnet/sdk:10.0-noble-arm64v8 AS build
RUN apt-get update && apt-get install -y ca-certificates gnupg
WORKDIR /source