Slice 1 of TASK 65, pure memory, no encoder change yet. A 1080p BGRA frame is 8.3 MB, so fifteen seconds of raw composite is ~7.4 GB — the 15 MB figure in the spec is the *compressed* stream. So the ring holds fragmented-MP4 bytes that ffmpeg will write to a pipe, and a save is init-segment + surviving fragments in order. Two rules the tests pin: the cap is by **bytes** (bitrate moves; seconds do not), and the newest fragments always win, because a clip that ends at the keypress is the entire point. The init segment does not rotate and cannot be reused across encoder starts, so `Clear` drops it too — an MP4 without its moov is noise. Next slice: ffmpeg writes the buffer output to stdout while `-stats` stays on stderr, and the pump feeds this ring. Not done here on purpose — an encoder change is the riskiest edit in the app and deserves its own commit and test.
69 lines
2.1 KiB
C#
69 lines
2.1 KiB
C#
using System;
|
|
using Xunit;
|
|
using ytLive.Services;
|
|
|
|
namespace ytLive.Tests;
|
|
|
|
/// <summary>
|
|
/// TASK 65 slice 1 — the replay ring as pure memory.
|
|
///
|
|
/// <para>The two rules that matter and are easy to break later: the cap is by <b>bytes</b> (bitrate moves,
|
|
/// seconds do not), and a snapshot is always init-segment-first, because an MP4 without its moov is not a
|
|
/// video, it is noise.</para>
|
|
/// </summary>
|
|
public sealed class ClipRingTests
|
|
{
|
|
[Fact]
|
|
public void Snapshot_IsInitThenFragmentsInOrder()
|
|
{
|
|
var ring = new ClipRing(1024);
|
|
ring.SetInit(new byte[] { 1, 2 });
|
|
ring.Append(new byte[] { 3 });
|
|
ring.Append(new byte[] { 4, 5 });
|
|
|
|
Assert.Equal(new byte[] { 1, 2, 3, 4, 5 }, ring.Snapshot());
|
|
Assert.Equal(5, ring.Bytes);
|
|
Assert.Equal(2, ring.FragmentCount);
|
|
}
|
|
|
|
[Fact]
|
|
public void TheCapDropsOldestFragments_SoTheClipEndsAtTheKeypress()
|
|
{
|
|
var ring = new ClipRing(capBytes: 10);
|
|
ring.SetInit(new byte[2]);
|
|
for (var i = 0; i < 5; i++) ring.Append(new byte[3]); // 2 + 15 = 17 → trims
|
|
|
|
Assert.True(ring.Bytes <= 10);
|
|
Assert.Equal(2, ring.FragmentCount); // newest two survive
|
|
|
|
var snapshot = ring.Snapshot();
|
|
Assert.Equal(2 + 3 + 3, snapshot.Length);
|
|
}
|
|
|
|
[Fact]
|
|
public void NothingBuffered_SnapshotsEmpty_SoTheCallerCannotWriteAZeroByteFile()
|
|
{
|
|
var ring = new ClipRing(1024);
|
|
Assert.True(ring.IsEmpty);
|
|
Assert.Empty(ring.Snapshot());
|
|
|
|
ring.SetInit(new byte[4]);
|
|
Assert.True(ring.IsEmpty); // init alone is not a clip
|
|
Assert.Empty(ring.Snapshot());
|
|
}
|
|
|
|
[Fact]
|
|
public void ClearForgetsTheInit_SoTheNextEncoderStartCannotReuseAStaleHeader()
|
|
{
|
|
var ring = new ClipRing(1024);
|
|
ring.SetInit(new byte[] { 9 });
|
|
ring.Append(new byte[] { 8 });
|
|
ring.Clear();
|
|
|
|
Assert.True(ring.IsEmpty);
|
|
Assert.Equal(0, ring.Bytes);
|
|
ring.Append(new byte[] { 7 });
|
|
Assert.Empty(ring.Snapshot()); // still no init → not playable
|
|
}
|
|
}
|