diff --git a/Services/ClipRing.cs b/Services/ClipRing.cs
new file mode 100644
index 0000000..c99656f
--- /dev/null
+++ b/Services/ClipRing.cs
@@ -0,0 +1,91 @@
+using System;
+using System.Collections.Generic;
+
+namespace ytLive.Services;
+
+///
+/// TASK 65: the replay buffer's memory. ffmpeg writes fragmented MP4 to a pipe; the app keeps the last
+/// N seconds of those fragments here and hands them back whole.
+///
+/// Why encoded bytes and not frames: a 1080p BGRA composite frame is 8.3 MB, so fifteen
+/// seconds at 60 fps is ~7.4 GB of raw frames. The 15 MB figure is the compressed stream, and only that
+/// fits in RAM. Why whole fragments: a fragment is the smallest thing that can be replayed, so the
+/// cap is a byte cap and the clip may be slightly shorter than the requested seconds — never corrupt.
+///
+public sealed class ClipRing
+{
+ private readonly long _capBytes;
+ private readonly Queue _fragments = new();
+ private byte[]? _init;
+ private long _bytes;
+
+ public ClipRing(long capBytes)
+ {
+ if (capBytes <= 0) throw new ArgumentOutOfRangeException(nameof(capBytes));
+ _capBytes = capBytes;
+ }
+
+ /// The stream's initialisation segment (ftyp + moov). Every replayable file needs it, and
+ /// it does not rotate — it is written once per encoder start.
+ public void SetInit(byte[] init)
+ {
+ _init = init ?? throw new ArgumentNullException(nameof(init));
+ RecomputeBytes();
+ Trim();
+ }
+
+ public void Append(byte[] fragment)
+ {
+ if (fragment is null || fragment.Length == 0) return;
+ _fragments.Enqueue(fragment);
+ _bytes += fragment.Length;
+ Trim();
+ }
+
+ public long Bytes => _bytes;
+ public int FragmentCount => _fragments.Count;
+ public bool IsEmpty => _init is null || _fragments.Count == 0;
+
+ /// The bytes of a playable clip, init first. Empty array when nothing has been buffered —
+ /// the caller must not write a zero-byte file (a failed save is better than a file that won't open).
+ public byte[] Snapshot()
+ {
+ if (IsEmpty) return Array.Empty();
+ var total = _init!.Length;
+ foreach (var fragment in _fragments) total += fragment.Length;
+
+ var output = new byte[total];
+ var offset = 0;
+ Buffer.BlockCopy(_init, 0, output, offset, _init.Length);
+ offset += _init.Length;
+ foreach (var fragment in _fragments)
+ {
+ Buffer.BlockCopy(fragment, 0, output, offset, fragment.Length);
+ offset += fragment.Length;
+ }
+ return output;
+ }
+
+ public void Clear()
+ {
+ _fragments.Clear();
+ _init = null;
+ _bytes = 0;
+ }
+
+ private void RecomputeBytes()
+ {
+ _bytes = _init?.Length ?? 0;
+ foreach (var fragment in _fragments) _bytes += fragment.Length;
+ }
+
+ /// Drop oldest fragments until the ring fits. Newest data always wins: a clip that ends at
+ /// the moment the creator pressed the key is the whole point.
+ private void Trim()
+ {
+ while (_fragments.Count > 0 && _bytes > _capBytes)
+ {
+ _bytes -= _fragments.Dequeue().Length;
+ }
+ }
+}
diff --git a/ytLive.Tests/ClipRingTests.cs b/ytLive.Tests/ClipRingTests.cs
new file mode 100644
index 0000000..b777b97
--- /dev/null
+++ b/ytLive.Tests/ClipRingTests.cs
@@ -0,0 +1,68 @@
+using System;
+using Xunit;
+using ytLive.Services;
+
+namespace ytLive.Tests;
+
+///
+/// TASK 65 slice 1 — the replay ring as pure memory.
+///
+/// The two rules that matter and are easy to break later: the cap is by bytes (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.
+///
+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
+ }
+}