diff --git a/js/hang/src/container/consumer.test.ts b/js/hang/src/container/consumer.test.ts index 180c814f0a..0bf62025ed 100644 --- a/js/hang/src/container/consumer.test.ts +++ b/js/hang/src/container/consumer.test.ts @@ -161,7 +161,7 @@ test("Legacy Producer omits a reordered group's presentation endpoint marker", a producer.encode(new Uint8Array([1]), timestamp as Time.Micro, index === 0); } producer.encode(new Uint8Array([1]), 160_000 as Time.Micro, true); - producer.cut(200_000 as Time.Micro); + producer.discontinuity(200_000 as Time.Micro); producer.close(); const group = await subscriber.recvGroup(); expect(group).toBeDefined(); @@ -199,14 +199,14 @@ test("Legacy Producer estimates the tail from the current cadence", async () => expect(end).toBe(131_000); }); -test("Legacy Producer rejects a backwards cut without closing the group", async () => { +test("Legacy Producer rejects a backwards discontinuity without closing the group", async () => { const track = new Track.Producer("test"); const subscriber = track.subscribe({ maxAge: Time.Milli(30_000) }); const producer = new LegacyProducer(track, new LegacyFormat("video")); producer.encode(new Uint8Array([1]), 20_000 as Time.Micro, true); - expect(() => producer.cut(10_000 as Time.Micro)).toThrow(); + expect(() => producer.discontinuity(10_000 as Time.Micro)).toThrow(); producer.encode(new Uint8Array([2]), 30_000 as Time.Micro, false); - producer.cut(35_000 as Time.Micro); + producer.discontinuity(35_000 as Time.Micro); producer.close(); const group = await subscriber.recvGroup(); const timestamps = []; @@ -245,14 +245,14 @@ async function readGroups(subscriber: Track.Subscriber, last: number) { } } -test("Legacy Producer cut marks the break with one empty frame at the live edge", async () => { +test("Legacy Producer discontinuity marks the break with one empty frame at the live edge", async () => { const track = new Track.Producer("test"); const subscriber = replay(track); const producer = new LegacyProducer(track, new LegacyFormat("audio")); producer.encode(new Uint8Array([1]), 0 as Time.Micro, true); producer.encode(new Uint8Array([1]), 20_000 as Time.Micro, true); - producer.cut(); - producer.cut(); // nothing new to mark + producer.discontinuity(); + producer.discontinuity(); // nothing new to mark producer.encode(new Uint8Array([1]), 5_000_000 as Time.Micro, true); producer.close(); @@ -264,12 +264,12 @@ test("Legacy Producer cut marks the break with one empty frame at the live edge" ]); }); -test("Legacy Producer cut marks the break at the caller's end", async () => { +test("Legacy Producer discontinuity marks the break at the caller's end", async () => { const track = new Track.Producer("test"); const subscriber = replay(track); const producer = new LegacyProducer(track, new LegacyFormat("video")); producer.encode(new Uint8Array([1]), 0 as Time.Micro, true); - producer.cut(33_000 as Time.Micro); + producer.discontinuity(33_000 as Time.Micro); producer.close(); expect(await readGroups(subscriber, 1)).toEqual([ @@ -284,26 +284,26 @@ test("Legacy Producer cut marks the break at the caller's end", async () => { ]); }); -test("Legacy Producer cut marks nothing on a data track or before any frame", async () => { +test("Legacy Producer discontinuity marks nothing on a data track or before any frame", async () => { const data = new Track.Producer("data"); const producer = new LegacyProducer(data, new LegacyFormat("data")); producer.encode(new Uint8Array([1]), 0 as Time.Micro, true); - producer.cut(); + producer.discontinuity(); expect(data.appendGroup().sequence).toBe(1); const empty = new Track.Producer("empty"); - new LegacyProducer(empty, new LegacyFormat("video")).cut(); + new LegacyProducer(empty, new LegacyFormat("video")).discontinuity(); expect(empty.appendGroup().sequence).toBe(0); }); // A group's reach runs to its successor's first frame, so without the marker the group before a // pause would stretch across the whole gap and read as live to anyone joining after the resume. -test("Legacy Producer cut keeps pre-pause media from reading as live", async () => { +test("Legacy Producer discontinuity keeps pre-pause media from reading as live", async () => { const track = new Track.Producer("test"); const producer = new LegacyProducer(track, new LegacyFormat("video")); producer.encode(new Uint8Array([1]), 0 as Time.Micro, true); producer.encode(new Uint8Array([1]), 33_000 as Time.Micro, false); - producer.cut(); + producer.discontinuity(); producer.encode(new Uint8Array([1]), 5_000_000 as Time.Micro, true); producer.close(); @@ -1530,12 +1530,12 @@ test("live duration marker follows an immediately delivered video frame", async track.close(); }); -test("audio cut writes no duration marker", async () => { +test("audio discontinuity writes no duration marker", async () => { const track = new Track.Producer("test"); const subscriber = track.subscribe(); const producer = new LegacyProducer(track, new LegacyFormat("audio")); producer.encode(new Uint8Array([1]), 0 as Time.Micro, true); - producer.cut(15_000 as Time.Micro); + producer.discontinuity(15_000 as Time.Micro); const group = await subscriber.recvGroup(); expect(await group?.readFrame()).toBeDefined(); expect(await group?.readFrame()).toBeUndefined(); diff --git a/js/hang/src/container/legacy.ts b/js/hang/src/container/legacy.ts index c066dbe831..be7719d023 100644 --- a/js/hang/src/container/legacy.ts +++ b/js/hang/src/container/legacy.ts @@ -84,7 +84,7 @@ export class Producer { #liveEdge?: Time.Micro; // Gap between consecutive timestamps, used to close the last group when no successor exists. #interval?: Time.Micro; - // A cut's marker is the newest group, so another one would say nothing new. + // A discontinuity's marker is the newest group, so another one would say nothing new. #marked = false; /** Wrap a track to publish legacy-container frames into it. */ @@ -138,7 +138,7 @@ export class Producer { * joiner lands on. Data tracks only close the group, since an empty payload is data. No marker * is written until a frame follows the last one. Throws if `end` precedes the last video frame. */ - cut(end?: Time.Micro) { + discontinuity(end?: Time.Micro) { this.#close(end); // Nothing is measured across the break. this.#interval = undefined; diff --git a/js/publish/src/audio/encoder.ts b/js/publish/src/audio/encoder.ts index 0cab7ea977..52e441dd18 100644 --- a/js/publish/src/audio/encoder.ts +++ b/js/publish/src/audio/encoder.ts @@ -270,7 +270,7 @@ export class Encoder { }); // When demand disappears, end the epoch with a discontinuity marker (see - // Container.Legacy.Producer.cut) so a later subscriber resumes on the same track without the + // Container.Legacy.Producer.discontinuity) so a later subscriber resumes on the same track without the // pre-gap frames reading as live. Its empty payload marks where the submitted media ends. effect.run((effect) => { const track = effect.get(rendition.track); diff --git a/js/publish/src/video/encoder.test.ts b/js/publish/src/video/encoder.test.ts index 74759200d6..7f1ce33f69 100644 --- a/js/publish/src/video/encoder.test.ts +++ b/js/publish/src/video/encoder.test.ts @@ -90,9 +90,9 @@ test("encoding tracks encoder config in its child effect", async () => { } }); -test("a demand gap cuts the group and leaves the broadcast-owned track open for resume", async () => { +test("a demand gap marks a discontinuity and leaves the broadcast-owned track open for resume", async () => { using _videoEncoder = installFakeVideoEncoder(); - const cut = spyOn(Container.Legacy.Producer.prototype, "cut"); + const discontinuity = spyOn(Container.Legacy.Producer.prototype, "discontinuity"); const track = new Moq.Track.Producer("video").accept({ priority: 60 }); const live = new Signal(track); @@ -119,20 +119,20 @@ test("a demand gap cuts the group and leaves the broadcast-owned track open for live.set(undefined); await settle(); expect(track.closed.peek()).toBeUndefined(); - expect(cut).toHaveBeenCalledTimes(1); + expect(discontinuity).toHaveBeenCalledTimes(1); live.set(track); await settle(); expect(track.closed.peek()).toBeUndefined(); - cut.mockClear(); + discontinuity.mockClear(); track.close(); encoder.close(); - expect(cut).not.toHaveBeenCalled(); + expect(discontinuity).not.toHaveBeenCalled(); } finally { encoder.close(); track.close(); - cut.mockRestore(); + discontinuity.mockRestore(); } }); diff --git a/js/publish/src/video/encoder.ts b/js/publish/src/video/encoder.ts index a89c58b5b6..9964f8db3d 100644 --- a/js/publish/src/video/encoder.ts +++ b/js/publish/src/video/encoder.ts @@ -237,12 +237,12 @@ export class Encoder { this.#lastCaptureWall = performance.now(); const producer = new Container.Legacy.Producer(track, new Container.Legacy.Format("video")); - // The broadcast owns this static track across demand gaps. When demand disappears, cut the + // The broadcast owns this static track across demand gaps. When demand disappears, close the // current group, marking the break so a later subscriber resumes on the same track without // the pre-gap group reading as live. A fatal encoder error still aborts the track through // producer.close(err) below. effect.cleanup(() => { - if (track.closed.peek() === undefined) producer.cut(); + if (track.closed.peek() === undefined) producer.discontinuity(); }); let lastKeyframe: Time.Micro | undefined; diff --git a/quest/m2/README.md b/quest/m2/README.md index 1a0426d872..c74ce97c32 100644 --- a/quest/m2/README.md +++ b/quest/m2/README.md @@ -27,7 +27,6 @@ upstream release waits in [m4](/quest/m4/README.md). - [Audio loss recovery](/quest/m2/audio-loss-recovery.md) - prove a useful Opus recovery policy before exposing another option - [Opus implementation](/quest/m2/audio-opus-backend.md) - compare current codec quality, CPU, and optional build costs - [Latency ledger](/quest/m2/latency-ledger.md) - a session reports where its end-to-end audio delay went, stage by stage -- [JS discontinuity](/quest/m2/js-discontinuity.md) - JS names its timeline break `discontinuity()` like Rust, so `cut` means the same group close in both - [Synced data playback](/quest/m2/watch-data-sync.md) - js/watch releases JSON and binary payloads on the media playhead, and a slow data track holds media back - [Media Foundation decode](/quest/m2/audio-decode-mediafoundation.md) - Windows decodes HE-AAC, multichannel AAC, and what else the MFTs offer - [Media Foundation encode](/quest/m2/audio-encode-mediafoundation.md) - Windows encodes AAC-LC diff --git a/quest/m2/js-discontinuity.md b/quest/m2/js-discontinuity.md deleted file mode 100644 index a342c79b4e..0000000000 --- a/quest/m2/js-discontinuity.md +++ /dev/null @@ -1,24 +0,0 @@ -# [S] JS names the break discontinuity() - -## Goal - -`@moq/hang`'s container producer spells its two operations the way Rust -`moq_mux::container::Producer` does: `cut(end?)` closes the current group, -and `discontinuity()` closes it and writes the empty marker group that tells -subscribers to re-anchor. Today JS's public `cut()` writes the marker, so the -same name means a routine group close in Rust and a timeline break in JS. - -## Plan - -[#4045](https://github.com/moq-dev/moq/pull/4045) deletes -`quest/m1/js-publish-discontinuity.md`, since #3982 already put the marker -in `cut()`; this rename is the only remaining JS work. - -In `js/hang/src/container/legacy.ts`, rename the public `cut(end?)` to -`discontinuity()` (taking the same optional end) and keep the routine close -private until a caller needs it public. Move `js/publish/src/video/encoder.ts` -and any other caller over, and keep data tracks skipping a sequence the way -Rust's `discontinuity()` does if a JS data-track caller appears. Rust is -untouched. - -Public API: breaking in published `@moq/hang`, so it targets `dev`. Wire: none.