Implement Discontinuity Index for Live Streaming Enhancements
Added a DiscontinuityIndex property to ScheduleEntry and related DTOs to track the cumulative number of discontinuities in live streaming. Updated the LiveWindowCalculator to utilize this index for generating accurate EXT-X-DISCONTINUITY-SEQUENCE values, ensuring compatibility with ffmpeg-based players. Enhanced the WriteEntriesAsync method to calculate and persist the discontinuity index during entry creation. Updated relevant tests and documentation to reflect these changes, improving the reliability of live streaming functionality.
This commit is contained in:
@@ -27,7 +27,7 @@ public class LiveWindowCalculatorTests
|
||||
public void SingleEntry_ProducesWindowWithMonotonicMediaSequence()
|
||||
{
|
||||
var asset = Guid.NewGuid();
|
||||
var entries = new List<LiveEntry> { new(Epoch, Epoch.AddSeconds(100), asset, 50) };
|
||||
var entries = new List<LiveEntry> { new(Epoch, Epoch.AddSeconds(100), asset, 50, 3) };
|
||||
|
||||
// now = epoch + 20s → currentIndex = 10, окно [6..10].
|
||||
var playlist = LiveWindowCalculator.Build(Input(20, entries));
|
||||
@@ -38,6 +38,8 @@ public class LiveWindowCalculatorTests
|
||||
Assert.Equal([6, 7, 8, 9, 10], playlist.Segments.Select(s => s.LocalIndex));
|
||||
Assert.All(playlist.Segments, s => Assert.Equal(asset, s.AssetId));
|
||||
Assert.DoesNotContain(playlist.Segments, s => s.Discontinuity);
|
||||
// Окно целиком внутри записи — DISCONTINUITY-SEQUENCE равен её накопительному номеру.
|
||||
Assert.Equal(3, playlist.DiscontinuitySequence);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -47,8 +49,8 @@ public class LiveWindowCalculatorTests
|
||||
b = Guid.NewGuid();
|
||||
var entries = new List<LiveEntry>
|
||||
{
|
||||
new(Epoch, Epoch.AddSeconds(16), a, 8), // g0..7
|
||||
new(Epoch.AddSeconds(16), Epoch.AddSeconds(40), b, 12), // g8..
|
||||
new(Epoch, Epoch.AddSeconds(16), a, 8, 5), // g0..7
|
||||
new(Epoch.AddSeconds(16), Epoch.AddSeconds(40), b, 12, 6), // g8..
|
||||
};
|
||||
|
||||
var playlist = LiveWindowCalculator.Build(Input(20, entries));
|
||||
@@ -60,6 +62,8 @@ public class LiveWindowCalculatorTests
|
||||
[false, false, true, false, false],
|
||||
playlist.Segments.Select(s => s.Discontinuity)
|
||||
);
|
||||
// Первый сегмент окна принадлежит A — база берётся из его номера.
|
||||
Assert.Equal(5, playlist.DiscontinuitySequence);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -73,6 +77,8 @@ public class LiveWindowCalculatorTests
|
||||
Assert.Equal(5, playlist.Segments.Count);
|
||||
Assert.Equal([2, 3, 0, 1, 2], playlist.Segments.Select(s => s.LocalIndex));
|
||||
Assert.All(playlist.Segments, s => Assert.Equal(filler.AssetId, s.AssetId));
|
||||
// Чисто-филлерное окно склеек не содержит — DISCONTINUITY-SEQUENCE равен нулю.
|
||||
Assert.Equal(0, playlist.DiscontinuitySequence);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -81,7 +87,7 @@ public class LiveWindowCalculatorTests
|
||||
var b = Guid.NewGuid();
|
||||
var entries = new List<LiveEntry>
|
||||
{
|
||||
new(Epoch.AddSeconds(16), Epoch.AddSeconds(40), b, 12),
|
||||
new(Epoch.AddSeconds(16), Epoch.AddSeconds(40), b, 12, 4),
|
||||
};
|
||||
|
||||
// Окно [6..10]: g6,g7 без записи и без филлера → отброшены, старт с g8.
|
||||
@@ -91,6 +97,35 @@ public class LiveWindowCalculatorTests
|
||||
Assert.Equal(3, playlist.Segments.Count);
|
||||
Assert.Equal([0, 1, 2], playlist.Segments.Select(s => s.LocalIndex));
|
||||
Assert.DoesNotContain(playlist.Segments, s => s.Discontinuity);
|
||||
Assert.Equal(4, playlist.DiscontinuitySequence);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void FillerBetweenEntries_KeepsDiscontinuitySequenceConsistentAsWindowSlides()
|
||||
{
|
||||
// A g0..4, разрыв g5 (крутится филлер), B с g6. Номера записей отстоят на 2 (вход-в-филлер +
|
||||
// выход-из-него) — ровно столько склеек и вставит калькулятор между ними.
|
||||
Guid a = Guid.NewGuid(),
|
||||
b = Guid.NewGuid();
|
||||
var entries = new List<LiveEntry>
|
||||
{
|
||||
new(Epoch, Epoch.AddSeconds(10), a, 5, 10), // g0..4
|
||||
new(Epoch.AddSeconds(12), Epoch.AddSeconds(40), b, 14, 12), // g6..
|
||||
};
|
||||
var filler = new LiveFiller(Guid.NewGuid(), 4);
|
||||
|
||||
// Окно [4..8]: хвост A, филлер, голова B — база отматывается к номеру A.
|
||||
var mixed = LiveWindowCalculator.Build(Input(16, entries, filler));
|
||||
Assert.Equal(10, mixed.DiscontinuitySequence);
|
||||
Assert.Equal(
|
||||
[false, true, true, false, false],
|
||||
mixed.Segments.Select(s => s.Discontinuity)
|
||||
);
|
||||
|
||||
// Окно [6..10]: только B — обе склейки (A→филлер, филлер→B) ушли за край, +2 к номеру.
|
||||
var afterwards = LiveWindowCalculator.Build(Input(20, entries, filler));
|
||||
Assert.Equal(12, afterwards.DiscontinuitySequence);
|
||||
Assert.All(afterwards.Segments, s => Assert.Equal(b, s.AssetId));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
||||
Reference in New Issue
Block a user