using System; using System.Collections.Generic; using System.Linq; using System.Text; using Xunit; using ytLive.Services; using ytLive.Services.Encoder; namespace ytLive.Tests; /// /// TASK 65 slice 2 — the clip mux as a side channel. /// /// Two things are pinned here and both protect the broadcast: the command line gains the clip /// output ONLY when clips are enabled (otherwise ffmpeg's args are byte-for-byte what they were), and the /// byte splitter turns ffmpeg's fragmented MP4 into whole fragments without ever seeing the stream path. /// public sealed class ClipStreamTests { private static byte[] Box(string type, int payloadBytes) { var payload = Enumerable.Repeat((byte)0x41, payloadBytes).ToArray(); var box = new byte[8 + payloadBytes]; var size = box.Length; box[0] = (byte)(size >> 24); box[1] = (byte)(size >> 16); box[2] = (byte)(size >> 8); box[3] = (byte)size; Encoding.ASCII.GetBytes(type).CopyTo(box, 4); payload.CopyTo(box, 8); return box; } [Fact] public void TheClipOutputIsAppendedOnlyWhenClipsAreEnabled() { var plain = FfmpegArgs.Build(Options(clip: null), "libopenh264"); var withClips = FfmpegArgs.Build(Options(clip: new ClipRing(1 << 20)), "libopenh264"); Assert.DoesNotContain("pipe:1", plain); Assert.Contains("pipe:1", withClips); // The stream block must not move: everything up to the clip output is identical. // The clip block is appended, so the original command line is a strict prefix of the new one. Assert.Equal(plain, withClips.Take(plain.Count).ToList()); } [Fact] public void SplitterFeedsInitAndWholeFragments_EvenWhenBytesArriveInPieces() { var ring = new ClipRing(1 << 20); var splitter = new Fmp4FragmentSplitter(ring); var stream = new List(); stream.AddRange(Box("ftyp", 16)); stream.AddRange(Box("moov", 32)); stream.AddRange(Box("moof", 8)); stream.AddRange(Box("mdat", 40)); stream.AddRange(Box("moof", 8)); stream.AddRange(Box("mdat", 40)); // Deliberately ragged writes: a box must survive being cut anywhere. var bytes = stream.ToArray(); var offset = 0; foreach (var chunk in new[] { 3, 7, 11, 29, 1000 }) { var take = Math.Min(chunk, bytes.Length - offset); if (take <= 0) break; splitter.Write(bytes.AsSpan(offset, take)); offset += take; } if (offset < bytes.Length) splitter.Write(bytes.AsSpan(offset)); Assert.Equal(2, ring.FragmentCount); var snapshot = ring.Snapshot(); Assert.True(snapshot.Length > 0); Assert.Equal("ftyp", Encoding.ASCII.GetString(snapshot, 4, 4)); // init comes first } [Fact] public void MdatWithoutAMoofIsNotReplayable_AndIsIgnored() { var ring = new ClipRing(1 << 20); var splitter = new Fmp4FragmentSplitter(ring); splitter.Write(Box("ftyp", 8)); splitter.Write(Box("moov", 8)); splitter.Write(Box("mdat", 32)); // no moof → not a fragment Assert.Empty(ring.Snapshot()); // init alone is not a clip Assert.Equal(0, ring.FragmentCount); } private static EncoderOptions Options(ClipRing? clip) => new() { RtmpUrl = "rtmp://a.rtmp.youtube.com/live2/key", Width = 1920, Height = 1080, Fps = 30, BitrateKbps = 6000, StreamEnabled = true, ClipBuffer = clip, }; }