Browse Source

log problematic playlists (#640)

pull/641/head
Jason Dove 4 years ago committed by GitHub
parent
commit
1d6279cee8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 9
      ErsatzTV.Application/Streaming/Commands/StartFFmpegSessionHandler.cs
  2. 28
      ErsatzTV.Application/Streaming/HlsSessionWorker.cs
  3. 1
      ErsatzTV.Core.Tests/ErsatzTV.Core.Tests.csproj
  4. 34
      ErsatzTV.Core.Tests/FFmpeg/HlsPlaylistFilterTests.cs
  5. 174
      ErsatzTV.Core/FFmpeg/HlsPlaylistFilter.cs
  6. 18
      ErsatzTV.Core/FFmpeg/IHlsPlaylistFilter.cs
  7. 4
      ErsatzTV.Core/FFmpeg/TempFileCategory.cs
  8. 7
      ErsatzTV/Controllers/IptvController.cs
  9. 1
      ErsatzTV/Startup.cs

9
ErsatzTV.Application/Streaming/Commands/StartFFmpegSessionHandler.cs

@ -22,6 +22,7 @@ namespace ErsatzTV.Application.Streaming.Commands
private readonly IServiceScopeFactory _serviceScopeFactory; private readonly IServiceScopeFactory _serviceScopeFactory;
private readonly IFFmpegSegmenterService _ffmpegSegmenterService; private readonly IFFmpegSegmenterService _ffmpegSegmenterService;
private readonly IConfigElementRepository _configElementRepository; private readonly IConfigElementRepository _configElementRepository;
private readonly IHlsPlaylistFilter _hlsPlaylistFilter;
private readonly ILocalFileSystem _localFileSystem; private readonly ILocalFileSystem _localFileSystem;
public StartFFmpegSessionHandler( public StartFFmpegSessionHandler(
@ -29,13 +30,15 @@ namespace ErsatzTV.Application.Streaming.Commands
ILogger<StartFFmpegSessionHandler> logger, ILogger<StartFFmpegSessionHandler> logger,
IServiceScopeFactory serviceScopeFactory, IServiceScopeFactory serviceScopeFactory,
IFFmpegSegmenterService ffmpegSegmenterService, IFFmpegSegmenterService ffmpegSegmenterService,
IConfigElementRepository configElementRepository) IConfigElementRepository configElementRepository,
IHlsPlaylistFilter hlsPlaylistFilter)
{ {
_localFileSystem = localFileSystem; _localFileSystem = localFileSystem;
_logger = logger; _logger = logger;
_serviceScopeFactory = serviceScopeFactory; _serviceScopeFactory = serviceScopeFactory;
_ffmpegSegmenterService = ffmpegSegmenterService; _ffmpegSegmenterService = ffmpegSegmenterService;
_configElementRepository = configElementRepository; _configElementRepository = configElementRepository;
_hlsPlaylistFilter = hlsPlaylistFilter;
} }
public Task<Either<BaseError, Unit>> Handle(StartFFmpegSession request, CancellationToken cancellationToken) => public Task<Either<BaseError, Unit>> Handle(StartFFmpegSession request, CancellationToken cancellationToken) =>
@ -78,7 +81,7 @@ namespace ErsatzTV.Application.Streaming.Commands
return Unit.Default; return Unit.Default;
} }
private static async Task WaitForPlaylistSegments(string playlistFileName, int initialSegmentCount, IHlsSessionWorker worker) private async Task WaitForPlaylistSegments(string playlistFileName, int initialSegmentCount, IHlsSessionWorker worker)
{ {
while (!File.Exists(playlistFileName)) while (!File.Exists(playlistFileName))
{ {
@ -92,7 +95,7 @@ namespace ErsatzTV.Application.Streaming.Commands
DateTimeOffset now = DateTimeOffset.Now.AddSeconds(-30); DateTimeOffset now = DateTimeOffset.Now.AddSeconds(-30);
string[] input = await File.ReadAllLinesAsync(playlistFileName); string[] input = await File.ReadAllLinesAsync(playlistFileName);
TrimPlaylistResult result = HlsPlaylistFilter.TrimPlaylist(worker.PlaylistStart, now, input); TrimPlaylistResult result = _hlsPlaylistFilter.TrimPlaylist(worker.PlaylistStart, now, input);
segmentCount = result.SegmentCount; segmentCount = result.SegmentCount;
} }
} }

28
ErsatzTV.Application/Streaming/HlsSessionWorker.cs

@ -24,6 +24,7 @@ namespace ErsatzTV.Application.Streaming
public class HlsSessionWorker : IHlsSessionWorker public class HlsSessionWorker : IHlsSessionWorker
{ {
private static int _workAheadCount; private static int _workAheadCount;
private readonly IHlsPlaylistFilter _hlsPlaylistFilter;
private readonly IServiceScopeFactory _serviceScopeFactory; private readonly IServiceScopeFactory _serviceScopeFactory;
private readonly ILogger<HlsSessionWorker> _logger; private readonly ILogger<HlsSessionWorker> _logger;
private DateTimeOffset _lastAccess; private DateTimeOffset _lastAccess;
@ -33,8 +34,9 @@ namespace ErsatzTV.Application.Streaming
private DateTimeOffset _playlistStart; private DateTimeOffset _playlistStart;
private Option<int> _targetFramerate; private Option<int> _targetFramerate;
public HlsSessionWorker(IServiceScopeFactory serviceScopeFactory, ILogger<HlsSessionWorker> logger) public HlsSessionWorker(IHlsPlaylistFilter hlsPlaylistFilter, IServiceScopeFactory serviceScopeFactory, ILogger<HlsSessionWorker> logger)
{ {
_hlsPlaylistFilter = hlsPlaylistFilter;
_serviceScopeFactory = serviceScopeFactory; _serviceScopeFactory = serviceScopeFactory;
_logger = logger; _logger = logger;
} }
@ -123,7 +125,11 @@ namespace ErsatzTV.Application.Streaming
} }
} }
private async Task<bool> Transcode(string channelNumber, bool firstProcess, bool realtime, CancellationToken cancellationToken) private async Task<bool> Transcode(
string channelNumber,
bool firstProcess,
bool realtime,
CancellationToken cancellationToken)
{ {
try try
{ {
@ -212,7 +218,7 @@ namespace ErsatzTV.Application.Streaming
return true; return true;
} }
private async Task TrimAndDelete(string channelNumber, CancellationToken cancellationToken) private async Task TrimAndDelete(string channelNumber, CancellationToken cancellationToken)
{ {
string playlistFileName = Path.Combine( string playlistFileName = Path.Combine(
@ -224,7 +230,7 @@ namespace ErsatzTV.Application.Streaming
{ {
// trim playlist and insert discontinuity before appending with new ffmpeg process // trim playlist and insert discontinuity before appending with new ffmpeg process
string[] lines = await File.ReadAllLinesAsync(playlistFileName, cancellationToken); string[] lines = await File.ReadAllLinesAsync(playlistFileName, cancellationToken);
TrimPlaylistResult trimResult = HlsPlaylistFilter.TrimPlaylistWithDiscontinuity( TrimPlaylistResult trimResult = _hlsPlaylistFilter.TrimPlaylistWithDiscontinuity(
_playlistStart, _playlistStart,
DateTimeOffset.Now.AddMinutes(-1), DateTimeOffset.Now.AddMinutes(-1),
lines); lines);
@ -246,13 +252,13 @@ namespace ErsatzTV.Application.Streaming
var toDelete = allSegments.Filter(s => s.SequenceNumber < trimResult.Sequence).ToList(); var toDelete = allSegments.Filter(s => s.SequenceNumber < trimResult.Sequence).ToList();
// if (toDelete.Count > 0) // if (toDelete.Count > 0)
// { // {
// _logger.LogInformation( // _logger.LogInformation(
// "Deleting HLS segments {Min} to {Max} (less than {StartSequence})", // "Deleting HLS segments {Min} to {Max} (less than {StartSequence})",
// toDelete.Map(s => s.SequenceNumber).Min(), // toDelete.Map(s => s.SequenceNumber).Min(),
// toDelete.Map(s => s.SequenceNumber).Max(), // toDelete.Map(s => s.SequenceNumber).Max(),
// trimResult.Sequence); // trimResult.Sequence);
// } // }
foreach (Segment segment in toDelete) foreach (Segment segment in toDelete)
{ {
File.Delete(segment.File); File.Delete(segment.File);
@ -261,7 +267,7 @@ namespace ErsatzTV.Application.Streaming
_playlistStart = trimResult.PlaylistStart; _playlistStart = trimResult.PlaylistStart;
} }
} }
private async Task<long> GetPtsOffset(IMediator mediator, string channelNumber, CancellationToken cancellationToken) private async Task<long> GetPtsOffset(IMediator mediator, string channelNumber, CancellationToken cancellationToken)
{ {
var directory = new DirectoryInfo(Path.Combine(FileSystemLayout.TranscodeFolder, channelNumber)); var directory = new DirectoryInfo(Path.Combine(FileSystemLayout.TranscodeFolder, channelNumber));

1
ErsatzTV.Core.Tests/ErsatzTV.Core.Tests.csproj

@ -10,6 +10,7 @@
<PackageReference Include="LanguageExt.Core" Version="4.0.3" /> <PackageReference Include="LanguageExt.Core" Version="4.0.3" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="6.0.0" /> <PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="6.0.0" />
<PackageReference Include="Microsoft.Extensions.Logging" Version="6.0.0" /> <PackageReference Include="Microsoft.Extensions.Logging" Version="6.0.0" />
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="6.0.0" />
<PackageReference Include="Microsoft.Extensions.Logging.Debug" Version="6.0.0" /> <PackageReference Include="Microsoft.Extensions.Logging.Debug" Version="6.0.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.0.0" /> <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.0.0" />
<PackageReference Include="Microsoft.VisualStudio.Threading.Analyzers" Version="17.0.64"> <PackageReference Include="Microsoft.VisualStudio.Threading.Analyzers" Version="17.0.64">

34
ErsatzTV.Core.Tests/FFmpeg/HlsPlaylistFilterTests.cs

@ -1,6 +1,9 @@
using System; using System;
using ErsatzTV.Core.FFmpeg; using ErsatzTV.Core.FFmpeg;
using ErsatzTV.Core.Interfaces.FFmpeg;
using FluentAssertions; using FluentAssertions;
using Microsoft.Extensions.Logging;
using Moq;
using NUnit.Framework; using NUnit.Framework;
namespace ErsatzTV.Core.Tests.FFmpeg namespace ErsatzTV.Core.Tests.FFmpeg
@ -8,8 +11,19 @@ namespace ErsatzTV.Core.Tests.FFmpeg
[TestFixture] [TestFixture]
public class HlsPlaylistFilterTests public class HlsPlaylistFilterTests
{ {
private HlsPlaylistFilter _hlsPlaylistFilter;
[SetUp]
public void SetUp()
{
_hlsPlaylistFilter = new HlsPlaylistFilter(
new Mock<ITempFilePool>().Object,
new Mock<ILogger<HlsPlaylistFilter>>().Object
);
}
[Test] [Test]
public void HlsPlaylistFilter_ShouldRewriteProgramDateTime() public void _hlsPlaylistFilter_ShouldRewriteProgramDateTime()
{ {
var start = new DateTimeOffset(2021, 10, 9, 8, 0, 0, TimeSpan.FromHours(-5)); var start = new DateTimeOffset(2021, 10, 9, 8, 0, 0, TimeSpan.FromHours(-5));
string[] input = NormalizeLineEndings(@"#EXTM3U string[] input = NormalizeLineEndings(@"#EXTM3U
@ -28,7 +42,7 @@ live001138.ts
#EXT-X-PROGRAM-DATE-TIME:2021-10-08T08:34:57.320-0500 #EXT-X-PROGRAM-DATE-TIME:2021-10-08T08:34:57.320-0500
live001139.ts").Split(Environment.NewLine); live001139.ts").Split(Environment.NewLine);
TrimPlaylistResult result = HlsPlaylistFilter.TrimPlaylist(start, start.AddSeconds(-30), input); TrimPlaylistResult result = _hlsPlaylistFilter.TrimPlaylist(start, start.AddSeconds(-30), input);
result.PlaylistStart.Should().Be(start); result.PlaylistStart.Should().Be(start);
result.Sequence.Should().Be(1137); result.Sequence.Should().Be(1137);
@ -53,7 +67,7 @@ live001139.ts
} }
[Test] [Test]
public void HlsPlaylistFilter_ShouldLimitSegments() public void _hlsPlaylistFilter_ShouldLimitSegments()
{ {
var start = new DateTimeOffset(2021, 10, 9, 8, 0, 0, TimeSpan.FromHours(-5)); var start = new DateTimeOffset(2021, 10, 9, 8, 0, 0, TimeSpan.FromHours(-5));
string[] input = NormalizeLineEndings(@"#EXTM3U string[] input = NormalizeLineEndings(@"#EXTM3U
@ -72,7 +86,7 @@ live001138.ts
#EXT-X-PROGRAM-DATE-TIME:2021-10-08T08:34:57.320-0500 #EXT-X-PROGRAM-DATE-TIME:2021-10-08T08:34:57.320-0500
live001139.ts").Split(Environment.NewLine); live001139.ts").Split(Environment.NewLine);
TrimPlaylistResult result = HlsPlaylistFilter.TrimPlaylist(start, start.AddSeconds(-30), input, 2); TrimPlaylistResult result = _hlsPlaylistFilter.TrimPlaylist(start, start.AddSeconds(-30), input, 2);
result.PlaylistStart.Should().Be(start); result.PlaylistStart.Should().Be(start);
result.Sequence.Should().Be(1137); result.Sequence.Should().Be(1137);
@ -94,7 +108,7 @@ live001138.ts
} }
[Test] [Test]
public void HlsPlaylistFilter_ShouldAddDiscontinuity() public void _hlsPlaylistFilter_ShouldAddDiscontinuity()
{ {
var start = new DateTimeOffset(2021, 10, 9, 8, 0, 0, TimeSpan.FromHours(-5)); var start = new DateTimeOffset(2021, 10, 9, 8, 0, 0, TimeSpan.FromHours(-5));
string[] input = NormalizeLineEndings(@"#EXTM3U string[] input = NormalizeLineEndings(@"#EXTM3U
@ -113,7 +127,7 @@ live001138.ts
#EXT-X-PROGRAM-DATE-TIME:2021-10-08T08:34:57.320-0500 #EXT-X-PROGRAM-DATE-TIME:2021-10-08T08:34:57.320-0500
live001139.ts").Split(Environment.NewLine); live001139.ts").Split(Environment.NewLine);
TrimPlaylistResult result = HlsPlaylistFilter.TrimPlaylist( TrimPlaylistResult result = _hlsPlaylistFilter.TrimPlaylist(
start, start,
start.AddSeconds(-30), start.AddSeconds(-30),
input, input,
@ -144,7 +158,7 @@ live001139.ts
} }
[Test] [Test]
public void HlsPlaylistFilter_ShouldFilterOldSegments() public void _hlsPlaylistFilter_ShouldFilterOldSegments()
{ {
var start = new DateTimeOffset(2021, 10, 9, 8, 0, 0, TimeSpan.FromHours(-5)); var start = new DateTimeOffset(2021, 10, 9, 8, 0, 0, TimeSpan.FromHours(-5));
string[] input = NormalizeLineEndings(@"#EXTM3U string[] input = NormalizeLineEndings(@"#EXTM3U
@ -163,7 +177,7 @@ live001138.ts
#EXT-X-PROGRAM-DATE-TIME:2021-10-08T08:34:57.320-0500 #EXT-X-PROGRAM-DATE-TIME:2021-10-08T08:34:57.320-0500
live001139.ts").Split(Environment.NewLine); live001139.ts").Split(Environment.NewLine);
TrimPlaylistResult result = HlsPlaylistFilter.TrimPlaylist(start, start.AddSeconds(6), input); TrimPlaylistResult result = _hlsPlaylistFilter.TrimPlaylist(start, start.AddSeconds(6), input);
result.PlaylistStart.Should().Be(start.AddSeconds(8)); result.PlaylistStart.Should().Be(start.AddSeconds(8));
result.Sequence.Should().Be(1139); result.Sequence.Should().Be(1139);
@ -182,7 +196,7 @@ live001139.ts
} }
[Test] [Test]
public void HlsPlaylistFilter_ShouldFilterOldDiscontinuity() public void _hlsPlaylistFilter_ShouldFilterOldDiscontinuity()
{ {
var start = new DateTimeOffset(2021, 10, 9, 8, 0, 0, TimeSpan.FromHours(-5)); var start = new DateTimeOffset(2021, 10, 9, 8, 0, 0, TimeSpan.FromHours(-5));
string[] input = NormalizeLineEndings(@"#EXTM3U string[] input = NormalizeLineEndings(@"#EXTM3U
@ -202,7 +216,7 @@ live001138.ts
#EXT-X-PROGRAM-DATE-TIME:2021-10-08T08:34:57.320-0500 #EXT-X-PROGRAM-DATE-TIME:2021-10-08T08:34:57.320-0500
live001139.ts").Split(Environment.NewLine); live001139.ts").Split(Environment.NewLine);
TrimPlaylistResult result = HlsPlaylistFilter.TrimPlaylist(start, start.AddSeconds(6), input); TrimPlaylistResult result = _hlsPlaylistFilter.TrimPlaylist(start, start.AddSeconds(6), input);
result.PlaylistStart.Should().Be(start.AddSeconds(8)); result.PlaylistStart.Should().Be(start.AddSeconds(8));
result.Sequence.Should().Be(1139); result.Sequence.Should().Be(1139);

174
ErsatzTV.Core/FFmpeg/HlsPlaylistFilter.cs

@ -1,110 +1,144 @@
using System; using System;
using System.Globalization; using System.Globalization;
using System.IO;
using System.Text; using System.Text;
using ErsatzTV.Core.Interfaces.FFmpeg;
using Microsoft.Extensions.Logging;
namespace ErsatzTV.Core.FFmpeg namespace ErsatzTV.Core.FFmpeg
{ {
public class HlsPlaylistFilter public class HlsPlaylistFilter : IHlsPlaylistFilter
{ {
public static TrimPlaylistResult TrimPlaylist( private readonly ITempFilePool _tempFilePool;
private readonly ILogger<HlsPlaylistFilter> _logger;
public HlsPlaylistFilter(ITempFilePool tempFilePool, ILogger<HlsPlaylistFilter> logger)
{
_tempFilePool = tempFilePool;
_logger = logger;
}
public TrimPlaylistResult TrimPlaylist(
DateTimeOffset playlistStart, DateTimeOffset playlistStart,
DateTimeOffset filterBefore, DateTimeOffset filterBefore,
string[] lines, string[] lines,
int maxSegments = 10, int maxSegments = 10,
bool endWithDiscontinuity = false) bool endWithDiscontinuity = false)
{ {
DateTimeOffset currentTime = playlistStart; try
DateTimeOffset nextPlaylistStart = DateTimeOffset.MaxValue;
var discontinuitySequence = 0;
var startSequence = 0;
var output = new StringBuilder();
var started = false;
var i = 0;
var segments = 0;
while (!lines[i].StartsWith("#EXTINF:"))
{ {
if (lines[i].StartsWith("#EXT-X-DISCONTINUITY-SEQUENCE")) DateTimeOffset currentTime = playlistStart;
{ DateTimeOffset nextPlaylistStart = DateTimeOffset.MaxValue;
discontinuitySequence = int.Parse(lines[i].Split(':')[1]);
}
i++;
}
while (i < lines.Length) var discontinuitySequence = 0;
{ var startSequence = 0;
if (segments >= maxSegments) var output = new StringBuilder();
var started = false;
var i = 0;
var segments = 0;
while (!lines[i].StartsWith("#EXTINF:"))
{ {
break; if (lines[i].StartsWith("#EXT-X-DISCONTINUITY-SEQUENCE"))
{
discontinuitySequence = int.Parse(lines[i].Split(':')[1]);
}
i++;
} }
string line = lines[i]; while (i < lines.Length)
// _logger.LogInformation("Line: {Line}", line);
if (line.StartsWith("#EXT-X-DISCONTINUITY"))
{ {
if (started) if (segments >= maxSegments)
{ {
output.AppendLine("#EXT-X-DISCONTINUITY"); break;
} }
else
string line = lines[i];
// _logger.LogInformation("Line: {Line}", line);
if (line.StartsWith("#EXT-X-DISCONTINUITY"))
{ {
discontinuitySequence++; if (started)
{
output.AppendLine("#EXT-X-DISCONTINUITY");
}
else
{
discontinuitySequence++;
}
i++;
continue;
} }
i++; var duration = TimeSpan.FromSeconds(
continue; double.Parse(
} lines[i].TrimEnd(',').Split(':')[1],
NumberStyles.Number,
CultureInfo.InvariantCulture));
if (currentTime < filterBefore)
{
currentTime += duration;
i += 3;
continue;
}
nextPlaylistStart = currentTime < nextPlaylistStart ? currentTime : nextPlaylistStart;
if (!started)
{
startSequence = int.Parse(lines[i + 2].Replace("live", string.Empty).Split('.')[0]);
output.AppendLine("#EXTM3U");
output.AppendLine("#EXT-X-VERSION:6");
output.AppendLine("#EXT-X-TARGETDURATION:4");
output.AppendLine($"#EXT-X-MEDIA-SEQUENCE:{startSequence}");
output.AppendLine($"#EXT-X-DISCONTINUITY-SEQUENCE:{discontinuitySequence}");
output.AppendLine("#EXT-X-INDEPENDENT-SEGMENTS");
output.AppendLine("#EXT-X-DISCONTINUITY");
started = true;
}
output.AppendLine(lines[i]);
string offset = currentTime.ToString("zzz").Replace(":", string.Empty);
output.AppendLine($"#EXT-X-PROGRAM-DATE-TIME:{currentTime:yyyy-MM-ddTHH:mm:ss.fff}{offset}");
output.AppendLine(lines[i + 2]);
var duration = TimeSpan.FromSeconds(
double.Parse(
lines[i].TrimEnd(',').Split(':')[1],
NumberStyles.Number,
CultureInfo.InvariantCulture));
if (currentTime < filterBefore)
{
currentTime += duration; currentTime += duration;
segments++;
i += 3; i += 3;
continue;
} }
nextPlaylistStart = currentTime < nextPlaylistStart ? currentTime : nextPlaylistStart;
if (!started) var playlist = output.ToString();
if (endWithDiscontinuity && !playlist.EndsWith($"#EXT-X-DISCONTINUITY{Environment.NewLine}"))
{ {
startSequence = int.Parse(lines[i + 2].Replace("live", string.Empty).Split('.')[0]); playlist += "#EXT-X-DISCONTINUITY" + Environment.NewLine;
output.AppendLine("#EXTM3U");
output.AppendLine("#EXT-X-VERSION:6");
output.AppendLine("#EXT-X-TARGETDURATION:4");
output.AppendLine($"#EXT-X-MEDIA-SEQUENCE:{startSequence}");
output.AppendLine($"#EXT-X-DISCONTINUITY-SEQUENCE:{discontinuitySequence}");
output.AppendLine("#EXT-X-INDEPENDENT-SEGMENTS");
output.AppendLine("#EXT-X-DISCONTINUITY");
started = true;
} }
output.AppendLine(lines[i]); return new TrimPlaylistResult(nextPlaylistStart, startSequence, playlist, segments);
string offset = currentTime.ToString("zzz").Replace(":", string.Empty);
output.AppendLine($"#EXT-X-PROGRAM-DATE-TIME:{currentTime:yyyy-MM-ddTHH:mm:ss.fff}{offset}");
output.AppendLine(lines[i + 2]);
currentTime += duration;
segments++;
i += 3;
} }
catch (Exception ex)
var playlist = output.ToString();
if (endWithDiscontinuity && !playlist.EndsWith($"#EXT-X-DISCONTINUITY{Environment.NewLine}"))
{ {
playlist += "#EXT-X-DISCONTINUITY" + Environment.NewLine; try
} {
string file = _tempFilePool.GetNextTempFile(TempFileCategory.BadPlaylist);
File.WriteAllLines(file, lines);
return new TrimPlaylistResult(nextPlaylistStart, startSequence, playlist, segments); _logger.LogError(ex, "Error filtering playlist. Bad playlist saved to {BadPlaylistFile}", file);
// TODO: better error result?
return new TrimPlaylistResult(playlistStart, 0, string.Empty, 0);
}
catch
{
// do nothing
}
throw;
}
} }
public static TrimPlaylistResult TrimPlaylistWithDiscontinuity( public TrimPlaylistResult TrimPlaylistWithDiscontinuity(
DateTimeOffset playlistStart, DateTimeOffset playlistStart,
DateTimeOffset filterBefore, DateTimeOffset filterBefore,
string[] lines) string[] lines)

18
ErsatzTV.Core/FFmpeg/IHlsPlaylistFilter.cs

@ -0,0 +1,18 @@
using System;
namespace ErsatzTV.Core.FFmpeg;
public interface IHlsPlaylistFilter
{
TrimPlaylistResult TrimPlaylist(
DateTimeOffset playlistStart,
DateTimeOffset filterBefore,
string[] lines,
int maxSegments = 10,
bool endWithDiscontinuity = false);
TrimPlaylistResult TrimPlaylistWithDiscontinuity(
DateTimeOffset playlistStart,
DateTimeOffset filterBefore,
string[] lines);
}

4
ErsatzTV.Core/FFmpeg/TempFileCategory.cs

@ -5,6 +5,8 @@
Subtitle = 0, Subtitle = 0,
SongBackground = 1, SongBackground = 1,
CoverArt = 2, CoverArt = 2,
CachedArtwork = 3 CachedArtwork = 3,
BadPlaylist = 99
} }
} }

7
ErsatzTV/Controllers/IptvController.cs

@ -26,17 +26,20 @@ namespace ErsatzTV.Controllers
public class IptvController : ControllerBase public class IptvController : ControllerBase
{ {
private readonly IFFmpegSegmenterService _ffmpegSegmenterService; private readonly IFFmpegSegmenterService _ffmpegSegmenterService;
private readonly IHlsPlaylistFilter _hlsPlaylistFilter;
private readonly ILogger<IptvController> _logger; private readonly ILogger<IptvController> _logger;
private readonly IMediator _mediator; private readonly IMediator _mediator;
public IptvController( public IptvController(
IMediator mediator, IMediator mediator,
ILogger<IptvController> logger, ILogger<IptvController> logger,
IFFmpegSegmenterService ffmpegSegmenterService) IFFmpegSegmenterService ffmpegSegmenterService,
IHlsPlaylistFilter hlsPlaylistFilter)
{ {
_mediator = mediator; _mediator = mediator;
_logger = logger; _logger = logger;
_ffmpegSegmenterService = ffmpegSegmenterService; _ffmpegSegmenterService = ffmpegSegmenterService;
_hlsPlaylistFilter = hlsPlaylistFilter;
} }
[HttpGet("iptv/channels.m3u")] [HttpGet("iptv/channels.m3u")]
@ -97,7 +100,7 @@ namespace ErsatzTV.Controllers
// worker.PlaylistStart, // worker.PlaylistStart,
// now); // now);
TrimPlaylistResult result = HlsPlaylistFilter.TrimPlaylist(worker.PlaylistStart, now, input); TrimPlaylistResult result = _hlsPlaylistFilter.TrimPlaylist(worker.PlaylistStart, now, input);
return Content(result.Playlist, "application/vnd.apple.mpegurl"); return Content(result.Playlist, "application/vnd.apple.mpegurl");
} }
} }

1
ErsatzTV/Startup.cs

@ -254,6 +254,7 @@ namespace ErsatzTV
services.AddSingleton<ISearchIndex, SearchIndex>(); services.AddSingleton<ISearchIndex, SearchIndex>();
services.AddSingleton<IFFmpegSegmenterService, FFmpegSegmenterService>(); services.AddSingleton<IFFmpegSegmenterService, FFmpegSegmenterService>();
services.AddSingleton<ITempFilePool, TempFilePool>(); services.AddSingleton<ITempFilePool, TempFilePool>();
services.AddSingleton<IHlsPlaylistFilter, HlsPlaylistFilter>();
AddChannel<IBackgroundServiceRequest>(services); AddChannel<IBackgroundServiceRequest>(services);
AddChannel<IPlexBackgroundServiceRequest>(services); AddChannel<IPlexBackgroundServiceRequest>(services);
AddChannel<IJellyfinBackgroundServiceRequest>(services); AddChannel<IJellyfinBackgroundServiceRequest>(services);

Loading…
Cancel
Save