Browse Source

rework mpeg-ts mode (#557)

pull/558/head
Jason Dove 4 years ago committed by GitHub
parent
commit
594ce437fb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 6
      CHANGELOG.md
  2. 5
      ErsatzTV.Application/Streaming/Queries/FFmpegProcessHandler.cs
  3. 2
      ErsatzTV.Application/Streaming/Queries/GetConcatProcessByChannelNumber.cs
  4. 21
      ErsatzTV.Application/Streaming/Queries/GetWrappedProcessByChannelNumber.cs
  5. 46
      ErsatzTV.Application/Streaming/Queries/GetWrappedProcessByChannelNumberHandler.cs
  6. 5
      ErsatzTV.Core/Domain/StreamingMode.cs
  7. 2
      ErsatzTV.Core/FFmpeg/FFmpegPlaybackSettingsCalculator.cs
  8. 7
      ErsatzTV.Core/FFmpeg/FFmpegProcessBuilder.cs
  9. 18
      ErsatzTV.Core/FFmpeg/FFmpegProcessService.cs
  10. 3
      ErsatzTV.Core/Hdhr/LineupItem.cs
  11. 2
      ErsatzTV.Core/Interfaces/FFmpeg/IFFmpegProcessService.cs
  12. 4
      ErsatzTV.Core/Iptv/ChannelPlaylist.cs
  13. 3855
      ErsatzTV.Infrastructure/Migrations/20220105234915_Remove_HLSHybridStreamingMode.Designer.cs
  14. 22
      ErsatzTV.Infrastructure/Migrations/20220105234915_Remove_HLSHybridStreamingMode.cs
  15. 2
      ErsatzTV.Infrastructure/Migrations/TvContextModelSnapshot.cs
  16. 2
      ErsatzTV/Controllers/InternalController.cs
  17. 17
      ErsatzTV/Controllers/IptvController.cs
  18. 6
      ErsatzTV/Pages/ChannelEditor.razor
  19. 4
      ErsatzTV/Pages/Channels.razor

6
CHANGELOG.md

@ -4,6 +4,12 @@ All notable changes to this project will be documented in this file. @@ -4,6 +4,12 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
## [Unreleased]
### Changed
- Remove `HLS Hybrid` streaming mode; all channels have been reconfigured to use the superior `HLS Segmenter` streaming mode
- Update `MPEG-TS` streaming mode to internally use the HLS segmenter
- This improves compatibility with many clients and also improves performance at program boundaries
- Renamed existing `MPEG-TS` mode as `MPEG-TS (Legacy)`
- This mode will be removed in a future release
## [0.3.5-alpha] - 2022-01-05
### Fixed

5
ErsatzTV.Application/Streaming/Queries/FFmpegProcessHandler.cs

@ -23,7 +23,7 @@ namespace ErsatzTV.Application.Streaming.Queries @@ -23,7 +23,7 @@ namespace ErsatzTV.Application.Streaming.Queries
public async Task<Either<BaseError, PlayoutItemProcessModel>> Handle(T request, CancellationToken cancellationToken)
{
await using TvContext dbContext = _dbContextFactory.CreateDbContext();
await using TvContext dbContext = await _dbContextFactory.CreateDbContextAsync(cancellationToken);
Validation<BaseError, Tuple<Channel, string>> validation = await Validate(dbContext, request);
return await validation.Match(
tuple => GetProcess(dbContext, request, tuple.Item1, tuple.Item2),
@ -56,7 +56,8 @@ namespace ErsatzTV.Application.Streaming.Queries @@ -56,7 +56,8 @@ namespace ErsatzTV.Application.Streaming.Queries
{
"hls-direct" => StreamingMode.HttpLiveStreamingDirect,
"segmenter" => StreamingMode.HttpLiveStreamingSegmenter,
"ts" => StreamingMode.TransportStream,
"ts" => StreamingMode.TransportStreamHybrid,
"ts-legacy" => StreamingMode.TransportStream,
_ => channel.StreamingMode
};

2
ErsatzTV.Application/Streaming/Queries/GetConcatProcessByChannelNumber.cs

@ -6,7 +6,7 @@ namespace ErsatzTV.Application.Streaming.Queries @@ -6,7 +6,7 @@ namespace ErsatzTV.Application.Streaming.Queries
{
public GetConcatProcessByChannelNumber(string scheme, string host, string channelNumber) : base(
channelNumber,
"ts",
"ts-legacy",
DateTimeOffset.Now,
false,
true)

21
ErsatzTV.Application/Streaming/Queries/GetWrappedProcessByChannelNumber.cs

@ -0,0 +1,21 @@ @@ -0,0 +1,21 @@
using System;
namespace ErsatzTV.Application.Streaming.Queries
{
public record GetWrappedProcessByChannelNumber : FFmpegProcessRequest
{
public GetWrappedProcessByChannelNumber(string scheme, string host, string channelNumber) : base(
channelNumber,
"ts",
DateTimeOffset.Now,
false,
true)
{
Scheme = scheme;
Host = host;
}
public string Scheme { get; }
public string Host { get; }
}
}

46
ErsatzTV.Application/Streaming/Queries/GetWrappedProcessByChannelNumberHandler.cs

@ -0,0 +1,46 @@ @@ -0,0 +1,46 @@
using System;
using System.Diagnostics;
using System.Threading.Tasks;
using ErsatzTV.Core;
using ErsatzTV.Core.Domain;
using ErsatzTV.Core.Interfaces.FFmpeg;
using ErsatzTV.Infrastructure.Data;
using ErsatzTV.Infrastructure.Extensions;
using LanguageExt;
using Microsoft.EntityFrameworkCore;
namespace ErsatzTV.Application.Streaming.Queries
{
public class GetWrappedProcessByChannelNumberHandler : FFmpegProcessHandler<GetWrappedProcessByChannelNumber>
{
private readonly IFFmpegProcessService _ffmpegProcessService;
public GetWrappedProcessByChannelNumberHandler(
IDbContextFactory<TvContext> dbContextFactory,
IFFmpegProcessService ffmpegProcessService)
: base(dbContextFactory)
{
_ffmpegProcessService = ffmpegProcessService;
}
protected override async Task<Either<BaseError, PlayoutItemProcessModel>> GetProcess(
TvContext dbContext,
GetWrappedProcessByChannelNumber request,
Channel channel,
string ffmpegPath)
{
bool saveReports = await dbContext.ConfigElements
.GetValue<bool>(ConfigElementKey.FFmpegSaveReports)
.Map(result => result.IfNone(false));
Process process = _ffmpegProcessService.WrapSegmenter(
ffmpegPath,
saveReports,
channel,
request.Scheme,
request.Host);
return new PlayoutItemProcessModel(process, DateTimeOffset.MaxValue);
}
}
}

5
ErsatzTV.Core/Domain/StreamingMode.cs

@ -4,7 +4,8 @@ @@ -4,7 +4,8 @@
{
TransportStream = 1,
HttpLiveStreamingDirect = 2,
HttpLiveStreamingHybrid = 3,
HttpLiveStreamingSegmenter = 4
// HttpLiveStreamingHybrid = 3,
HttpLiveStreamingSegmenter = 4,
TransportStreamHybrid = 5
}
}

2
ErsatzTV.Core/FFmpeg/FFmpegPlaybackSettingsCalculator.cs

@ -71,7 +71,7 @@ namespace ErsatzTV.Core.FFmpeg @@ -71,7 +71,7 @@ namespace ErsatzTV.Core.FFmpeg
result.VideoCodec = "copy";
result.Deinterlace = false;
break;
case StreamingMode.HttpLiveStreamingHybrid:
case StreamingMode.TransportStreamHybrid:
case StreamingMode.HttpLiveStreamingSegmenter:
case StreamingMode.TransportStream:
result.HardwareAcceleration = ffmpegProfile.HardwareAcceleration;

7
ErsatzTV.Core/FFmpeg/FFmpegProcessBuilder.cs

@ -202,6 +202,13 @@ namespace ErsatzTV.Core.FFmpeg @@ -202,6 +202,13 @@ namespace ErsatzTV.Core.FFmpeg
return this;
}
public FFmpegProcessBuilder WithCopyCodec()
{
_arguments.Add("-c");
_arguments.Add("copy");
return this;
}
public FFmpegProcessBuilder WithWatermark(
Option<WatermarkOptions> watermarkOptions,
IDisplaySize resolution)

18
ErsatzTV.Core/FFmpeg/FFmpegProcessService.cs

@ -247,6 +247,24 @@ namespace ErsatzTV.Core.FFmpeg @@ -247,6 +247,24 @@ namespace ErsatzTV.Core.FFmpeg
.Build();
}
public Process WrapSegmenter(string ffmpegPath, bool saveReports, Channel channel, string scheme, string host)
{
FFmpegPlaybackSettings playbackSettings = _playbackSettingsCalculator.ConcatSettings;
return new FFmpegProcessBuilder(ffmpegPath, saveReports, _logger)
.WithThreads(1)
.WithQuiet()
.WithFormatFlags(playbackSettings.FormatFlags)
.WithRealtimeOutput(playbackSettings.RealtimeOutput)
.WithInput($"http://localhost:{Settings.ListenPort}/iptv/channel/{channel.Number}.m3u8?mode=segmenter")
.WithMap("0")
.WithCopyCodec()
.WithMetadata(channel, None)
.WithFormat("mpegts")
.WithPipe()
.Build();
}
public Process ConvertToPng(string ffmpegPath, string inputFile, string outputFile)
{
return new FFmpegProcessBuilder(ffmpegPath, false, _logger)

3
ErsatzTV.Core/Hdhr/LineupItem.cs

@ -22,8 +22,7 @@ namespace ErsatzTV.Core.Hdhr @@ -22,8 +22,7 @@ namespace ErsatzTV.Core.Hdhr
public string URL => _channel.StreamingMode switch
{
StreamingMode.HttpLiveStreamingDirect or StreamingMode.HttpLiveStreamingHybrid =>
$"{_scheme}://{_host}/iptv/channel/{_channel.Number}.m3u8",
StreamingMode.HttpLiveStreamingDirect => $"{_scheme}://{_host}/iptv/channel/{_channel.Number}.m3u8",
_ => $"{_scheme}://{_host}/iptv/channel/{_channel.Number}.ts"
};
}

2
ErsatzTV.Core/Interfaces/FFmpeg/IFFmpegProcessService.cs

@ -38,6 +38,8 @@ namespace ErsatzTV.Core.Interfaces.FFmpeg @@ -38,6 +38,8 @@ namespace ErsatzTV.Core.Interfaces.FFmpeg
Process ConcatChannel(string ffmpegPath, bool saveReports, Channel channel, string scheme, string host);
Process WrapSegmenter(string ffmpegPath, bool saveReports, Channel channel, string scheme, string host);
Process ConvertToPng(string ffmpegPath, string inputFile, string outputFile);
Process ExtractAttachedPicAsPng(string ffmpegPath, string inputFile, int streamIndex, string outputFile);

4
ErsatzTV.Core/Iptv/ChannelPlaylist.cs

@ -43,9 +43,9 @@ namespace ErsatzTV.Core.Iptv @@ -43,9 +43,9 @@ namespace ErsatzTV.Core.Iptv
string format = channel.StreamingMode switch
{
StreamingMode.HttpLiveStreamingDirect => "m3u8?mode=hls-direct",
StreamingMode.HttpLiveStreamingHybrid => "m3u8",
StreamingMode.HttpLiveStreamingSegmenter => "m3u8?mode=segmenter",
_ => "ts"
StreamingMode.TransportStreamHybrid => "ts",
_ => "ts?mode=legacy"
};
string vcodec = channel.FFmpegProfile.VideoCodec.Split("_").Head();

3855
ErsatzTV.Infrastructure/Migrations/20220105234915_Remove_HLSHybridStreamingMode.Designer.cs generated

File diff suppressed because it is too large Load Diff

22
ErsatzTV.Infrastructure/Migrations/20220105234915_Remove_HLSHybridStreamingMode.cs

@ -0,0 +1,22 @@ @@ -0,0 +1,22 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace ErsatzTV.Infrastructure.Migrations
{
public partial class Remove_HLSHybridStreamingMode : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
// replace HLS Hybrid with HLS Segmenter
migrationBuilder.Sql("UPDATE Channel SET StreamingMode = 4 WHERE StreamingMode = 3");
// replace MPEG-TS (Legacy) with new MPEG-TS
migrationBuilder.Sql("UPDATE Channel SET StreamingMode = 5 WHERE StreamingMode = 1");
}
protected override void Down(MigrationBuilder migrationBuilder)
{
}
}
}

2
ErsatzTV.Infrastructure/Migrations/TvContextModelSnapshot.cs

@ -15,7 +15,7 @@ namespace ErsatzTV.Infrastructure.Migrations @@ -15,7 +15,7 @@ namespace ErsatzTV.Infrastructure.Migrations
protected override void BuildModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder.HasAnnotation("ProductVersion", "6.0.0");
modelBuilder.HasAnnotation("ProductVersion", "6.0.1");
modelBuilder.Entity("ErsatzTV.Core.Domain.Actor", b =>
{

2
ErsatzTV/Controllers/InternalController.cs

@ -27,7 +27,7 @@ namespace ErsatzTV.Controllers @@ -27,7 +27,7 @@ namespace ErsatzTV.Controllers
public Task<IActionResult> GetConcatPlaylist(string channelNumber) =>
_mediator.Send(new GetConcatPlaylistByChannelNumber(Request.Scheme, Request.Host.ToString(), channelNumber))
.ToActionResult();
[HttpGet("ffmpeg/stream/{channelNumber}")]
public Task<IActionResult> GetStream(
string channelNumber,

17
ErsatzTV/Controllers/IptvController.cs

@ -52,14 +52,24 @@ namespace ErsatzTV.Controllers @@ -52,14 +52,24 @@ namespace ErsatzTV.Controllers
.Map<ChannelGuide, IActionResult>(Ok);
[HttpGet("iptv/channel/{channelNumber}.ts")]
public Task<IActionResult> GetTransportStreamVideo(string channelNumber) =>
_mediator.Send(new GetConcatProcessByChannelNumber(Request.Scheme, Request.Host.ToString(), channelNumber))
public async Task<IActionResult> GetTransportStreamVideo(
string channelNumber,
[FromQuery]
string mode = null)
{
FFmpegProcessRequest request = mode switch
{
"legacy" => new GetConcatProcessByChannelNumber(Request.Scheme, Request.Host.ToString(), channelNumber),
_ => new GetWrappedProcessByChannelNumber(Request.Scheme, Request.Host.ToString(), channelNumber)
};
return await _mediator.Send(request)
.Map(
result => result.Match<IActionResult>(
processModel =>
{
Process process = processModel.Process;
_logger.LogInformation("Starting ts stream for channel {ChannelNumber}", channelNumber);
// _logger.LogDebug(
// "ffmpeg concat arguments {FFmpegArguments}",
@ -68,6 +78,7 @@ namespace ErsatzTV.Controllers @@ -68,6 +78,7 @@ namespace ErsatzTV.Controllers
return new FileStreamResult(process.StandardOutput.BaseStream, "video/mp2t");
},
error => BadRequest(error.Value)));
}
[HttpGet("iptv/session/{channelNumber}/hls.m3u8")]
public async Task<IActionResult> GetLivePlaylist(string channelNumber)

6
ErsatzTV/Pages/ChannelEditor.razor

@ -32,9 +32,9 @@ @@ -32,9 +32,9 @@
<MudTextField Label="Number" @bind-Value="_model.Number" For="@(() => _model.Number)" Immediate="true"/>
<MudTextField Class="mt-3" Label="Name" @bind-Value="_model.Name" For="@(() => _model.Name)"/>
<MudSelect Class="mt-3" Label="Streaming Mode" @bind-Value="_model.StreamingMode" For="@(() => _model.StreamingMode)">
<MudSelectItem Value="@(StreamingMode.TransportStream)">MPEG-TS</MudSelectItem>
<MudSelectItem Value="@(StreamingMode.TransportStreamHybrid)">MPEG-TS</MudSelectItem>
<MudSelectItem Value="@(StreamingMode.TransportStream)">MPEG-TS (Legacy)</MudSelectItem>
<MudSelectItem Value="@(StreamingMode.HttpLiveStreamingDirect)">HLS Direct</MudSelectItem>
<MudSelectItem Value="@(StreamingMode.HttpLiveStreamingHybrid)">HLS Hybrid</MudSelectItem>
<MudSelectItem Value="@(StreamingMode.HttpLiveStreamingSegmenter)">HLS Segmenter</MudSelectItem>
</MudSelect>
<MudSelect Class="mt-3" Label="FFmpeg Profile" @bind-Value="_model.FFmpegProfileId" For="@(() => _model.FFmpegProfileId)"
@ -154,7 +154,7 @@ @@ -154,7 +154,7 @@
_model.Number = (maxNumber + 1).ToString();
_model.Name = "New Channel";
_model.FFmpegProfileId = ffmpegSettings.DefaultFFmpegProfileId;
_model.StreamingMode = StreamingMode.TransportStream;
_model.StreamingMode = StreamingMode.TransportStreamHybrid;
}
}

4
ErsatzTV/Pages/Channels.razor

@ -140,9 +140,9 @@ @@ -140,9 +140,9 @@
private static string GetStreamingMode(StreamingMode streamingMode) => streamingMode switch {
StreamingMode.HttpLiveStreamingDirect => "HLS Direct",
StreamingMode.HttpLiveStreamingHybrid => "HLS Hybrid",
StreamingMode.HttpLiveStreamingSegmenter => "HLS Segmenter",
_ => "MPEG-TS"
StreamingMode.TransportStreamHybrid => "MPEG-TS",
_ => "MPEG-TS (Legacy)"
};
}
Loading…
Cancel
Save