diff --git a/CHANGELOG.md b/CHANGELOG.md index c504efe2..3187d3b1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -38,6 +38,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). - Fix playout detail table to only reload once when resetting a playout - Fix date formatting in playout detail table on reload (will now respect browser's `Accept-Language` header) - Use cache busting to avoid UI errors after upgrading the MudBlazor library +- Fix multi-variant playlist to report more accurate `BANDWIDTH` value based on ffmpeg profile ## [25.1.0] - 2025-01-10 ### Added diff --git a/ErsatzTV.Application/Channels/Mapper.cs b/ErsatzTV.Application/Channels/Mapper.cs index ff4a611c..a64bb9ca 100644 --- a/ErsatzTV.Application/Channels/Mapper.cs +++ b/ErsatzTV.Application/Channels/Mapper.cs @@ -39,6 +39,9 @@ internal static class Mapper internal static ResolutionViewModel ProjectToViewModel(Resolution resolution) => new(resolution.Height, resolution.Width); + internal static ResolutionAndBitrateViewModel ProjectToViewModel(Resolution resolution, int bitrate) => + new(resolution.Height, resolution.Width, bitrate); + private static string GetLogo(Channel channel) => Optional(channel.Artwork.FirstOrDefault(a => a.ArtworkKind == ArtworkKind.Logo)) .Match(a => a.Path, string.Empty); diff --git a/ErsatzTV.Application/Channels/Queries/GetChannelResolution.cs b/ErsatzTV.Application/Channels/Queries/GetChannelResolution.cs deleted file mode 100644 index 46b7d887..00000000 --- a/ErsatzTV.Application/Channels/Queries/GetChannelResolution.cs +++ /dev/null @@ -1,3 +0,0 @@ -namespace ErsatzTV.Application.Channels; - -public record GetChannelResolution(string ChannelNumber) : IRequest>; diff --git a/ErsatzTV.Application/Channels/Queries/GetChannelResolutionAndBitrate.cs b/ErsatzTV.Application/Channels/Queries/GetChannelResolutionAndBitrate.cs new file mode 100644 index 00000000..2b7cd74f --- /dev/null +++ b/ErsatzTV.Application/Channels/Queries/GetChannelResolutionAndBitrate.cs @@ -0,0 +1,3 @@ +namespace ErsatzTV.Application.Channels; + +public record GetChannelResolutionAndBitrate(string ChannelNumber) : IRequest>; diff --git a/ErsatzTV.Application/Channels/Queries/GetChannelResolutionHandler.cs b/ErsatzTV.Application/Channels/Queries/GetChannelResolutionAndBitrateHandler.cs similarity index 54% rename from ErsatzTV.Application/Channels/Queries/GetChannelResolutionHandler.cs rename to ErsatzTV.Application/Channels/Queries/GetChannelResolutionAndBitrateHandler.cs index 5d56df3d..3813d709 100644 --- a/ErsatzTV.Application/Channels/Queries/GetChannelResolutionHandler.cs +++ b/ErsatzTV.Application/Channels/Queries/GetChannelResolutionAndBitrateHandler.cs @@ -5,11 +5,11 @@ using Microsoft.EntityFrameworkCore; namespace ErsatzTV.Application.Channels; -public class GetChannelResolutionHandler(IDbContextFactory dbContextFactory) - : IRequestHandler> +public class GetChannelResolutionAndBitrateHandler(IDbContextFactory dbContextFactory) + : IRequestHandler> { - public async Task> Handle( - GetChannelResolution request, + public async Task> Handle( + GetChannelResolutionAndBitrate request, CancellationToken cancellationToken) { await using TvContext dbContext = await dbContextFactory.CreateDbContextAsync(cancellationToken); @@ -20,6 +20,8 @@ public class GetChannelResolutionHandler(IDbContextFactory dbContextF .ThenInclude(ff => ff.Resolution) .SelectOneAsync(c => c.Number, c => c.Number == request.ChannelNumber); - return maybeChannel.Map(c => Mapper.ProjectToViewModel(c.FFmpegProfile.Resolution)); + return maybeChannel.Map(c => Mapper.ProjectToViewModel( + c.FFmpegProfile.Resolution, + (int)((c.FFmpegProfile.VideoBitrate * 1000 + c.FFmpegProfile.AudioBitrate * 1000) * 1.2))); } } diff --git a/ErsatzTV.Application/Channels/ResolutionAndBitrateViewModel.cs b/ErsatzTV.Application/Channels/ResolutionAndBitrateViewModel.cs new file mode 100644 index 00000000..9bd7f8b3 --- /dev/null +++ b/ErsatzTV.Application/Channels/ResolutionAndBitrateViewModel.cs @@ -0,0 +1,3 @@ +namespace ErsatzTV.Application.Channels; + +public record ResolutionAndBitrateViewModel(int Height, int Width, int Bitrate); diff --git a/ErsatzTV/Controllers/IptvController.cs b/ErsatzTV/Controllers/IptvController.cs index feadd770..a473b02d 100644 --- a/ErsatzTV/Controllers/IptvController.cs +++ b/ErsatzTV/Controllers/IptvController.cs @@ -1,4 +1,5 @@ using System.Diagnostics; +using System.Globalization; using System.Text; using CliWrap; using ErsatzTV.Application.Channels; @@ -301,16 +302,18 @@ public class IptvController : ControllerBase "Failed to return ffmpeg multi-variant playlist; falling back to generated playlist"); } - Option maybeResolution = await _mediator.Send(new GetChannelResolution(channelNumber)); + Option maybeResolutionAndBitrate = await _mediator.Send(new GetChannelResolutionAndBitrate(channelNumber)); string resolution = string.Empty; - foreach (ResolutionViewModel res in maybeResolution) + string bitrate = "10000000"; + foreach (ResolutionAndBitrateViewModel res in maybeResolutionAndBitrate) { resolution = $",RESOLUTION={res.Width}x{res.Height}"; + bitrate = res.Bitrate.ToString(CultureInfo.InvariantCulture); } return $@"#EXTM3U #EXT-X-VERSION:3 -#EXT-X-STREAM-INF:BANDWIDTH=10000000{resolution} +#EXT-X-STREAM-INF:BANDWIDTH={bitrate}{resolution} {variantPlaylist}"; }