mirror of
https://github.com/zoriya/v10.git
synced 2026-08-16 02:45:09 +00:00
feat(spf): skip relocation for near-zero-PTS origins (spike)
Add NEAR_ZERO_ORIGIN_THRESHOLD (1s, absolute) to the derive: a shared/per-type origin below the threshold — including negatives — resolves to 0, so ordinary ~0-PTS VOD is left on its native timeline and no timestampOffset is set (the loader stamp already no-ops on a derived 0). Relocation stays targeted at streams with an intentional large origin (instant clips, bipbop @10s). Absolute basis, not proportional: the DTS-below-zero / edge-ripple risk scales with the origin's absolute size, not the presentation's duration. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
be8d28d687
commit
4c272f1890
@@ -74,6 +74,25 @@ function ownOrigin(data: MediaContainerData | undefined): number | undefined {
|
||||
: undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* Origins below this magnitude (seconds) are treated as `0` — the derive returns `0`, so
|
||||
* the presentation is left on its native (~0-based) timeline and no `timestampOffset` is
|
||||
* set (the loader stamp no-ops on a derived `0`). Ordinary VOD carries a small nonzero
|
||||
* encode origin (audio priming, first-frame CTS, edit lists); relocating by a sub-second
|
||||
* amount is pointless, and setting `timestampOffset` at all can ripple edge segments.
|
||||
* Relocation targets streams with an intentional large origin (instant clips, bipbop @10s).
|
||||
*
|
||||
* Absolute basis, not proportional: the DTS-below-zero / ripple risk scales with the
|
||||
* origin's absolute size, not with the presentation's duration. Also snaps negatives to
|
||||
* `0` — a negative origin would relocate *forward*, which is never the intent.
|
||||
*/
|
||||
export const NEAR_ZERO_ORIGIN_THRESHOLD = 1;
|
||||
|
||||
/** Snap a below-threshold (incl. negative) origin to `0` so it isn't relocated. */
|
||||
function thresholdOrigin(origin: number): number {
|
||||
return origin < NEAR_ZERO_ORIGIN_THRESHOLD ? 0 : origin;
|
||||
}
|
||||
|
||||
/**
|
||||
* The **default** — relocate the whole presentation by one shared origin: the `min`
|
||||
* across the *selected* A/V tracks' own origins, denormalized onto every type. This
|
||||
@@ -87,7 +106,8 @@ function ownOrigin(data: MediaContainerData | undefined): number | undefined {
|
||||
* Returns `undefined` for every type until all *selected* types have a complete origin
|
||||
* (the shared-`min` barrier). Which types must contribute is read from `ctx` (the
|
||||
* selected v/a ids); with no selection context it coordinates across whatever types
|
||||
* have data.
|
||||
* have data. A shared origin below {@link NEAR_ZERO_ORIGIN_THRESHOLD} is returned as `0`
|
||||
* (native — ordinary ~0-PTS VOD isn't relocated).
|
||||
*/
|
||||
export const deriveSharedMinStartMediaTime: DeriveStartMediaTime = (containerData, ctx) => {
|
||||
const contributingTypes: string[] = [];
|
||||
@@ -99,7 +119,7 @@ export const deriveSharedMinStartMediaTime: DeriveStartMediaTime = (containerDat
|
||||
// Barrier: not ready until every contributing type has a complete origin.
|
||||
if (origins.length === 0 || origins.some((origin) => origin === undefined)) return {};
|
||||
|
||||
const shared = Math.min(...(origins as number[]));
|
||||
const shared = thresholdOrigin(Math.min(...(origins as number[])));
|
||||
const out: Record<string, number | undefined> = {};
|
||||
for (const type of Object.keys(containerData)) out[type] = shared;
|
||||
return out;
|
||||
@@ -113,7 +133,10 @@ export const deriveSharedMinStartMediaTime: DeriveStartMediaTime = (containerDat
|
||||
*/
|
||||
export const derivePerTypeStartMediaTime: DeriveStartMediaTime = (containerData) => {
|
||||
const out: Record<string, number | undefined> = {};
|
||||
for (const [type, data] of Object.entries(containerData)) out[type] = ownOrigin(data);
|
||||
for (const [type, data] of Object.entries(containerData)) {
|
||||
const origin = ownOrigin(data);
|
||||
out[type] = origin === undefined ? undefined : thresholdOrigin(origin);
|
||||
}
|
||||
return out;
|
||||
};
|
||||
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import { derivePerTypeStartMediaTime, deriveSharedMinStartMediaTime } from '../establish-start-media-time';
|
||||
import {
|
||||
derivePerTypeStartMediaTime,
|
||||
deriveSharedMinStartMediaTime,
|
||||
NEAR_ZERO_ORIGIN_THRESHOLD,
|
||||
} from '../establish-start-media-time';
|
||||
|
||||
describe('deriveSharedMinStartMediaTime', () => {
|
||||
const sel = { selectedVideoTrackId: 'v', selectedAudioTrackId: 'a' };
|
||||
@@ -73,6 +77,43 @@ describe('deriveSharedMinStartMediaTime', () => {
|
||||
)
|
||||
).toEqual({ video: 9.956, audio: 9.956 });
|
||||
});
|
||||
|
||||
it('leaves ordinary ~0-PTS VOD native: a shared origin below the threshold returns 0', () => {
|
||||
// Both types carry a sub-second (0.5s) encode offset → not relocated.
|
||||
expect(
|
||||
deriveSharedMinStartMediaTime(
|
||||
{
|
||||
video: { timescale: 90000, baseMediaDecodeTime: 45000, segmentStartTime: 0 },
|
||||
audio: { timescale: 48000, baseMediaDecodeTime: 24000, segmentStartTime: 0 },
|
||||
},
|
||||
sel
|
||||
)
|
||||
).toEqual({ video: 0, audio: 0 });
|
||||
});
|
||||
|
||||
it('relocates at/above the threshold (the boundary is exclusive)', () => {
|
||||
const atThreshold = {
|
||||
video: { timescale: 90000, baseMediaDecodeTime: 90000 * NEAR_ZERO_ORIGIN_THRESHOLD, segmentStartTime: 0 },
|
||||
audio: { timescale: 48000, baseMediaDecodeTime: 48000 * NEAR_ZERO_ORIGIN_THRESHOLD, segmentStartTime: 0 },
|
||||
};
|
||||
expect(deriveSharedMinStartMediaTime(atThreshold, sel)).toEqual({
|
||||
video: NEAR_ZERO_ORIGIN_THRESHOLD,
|
||||
audio: NEAR_ZERO_ORIGIN_THRESHOLD,
|
||||
});
|
||||
});
|
||||
|
||||
it('snaps a negative shared origin to 0 (never relocates forward)', () => {
|
||||
// segmentStartTime > bmdt/ts → negative own origin.
|
||||
expect(
|
||||
deriveSharedMinStartMediaTime(
|
||||
{
|
||||
video: { timescale: 90000, baseMediaDecodeTime: 0, segmentStartTime: 5 },
|
||||
audio: { timescale: 48000, baseMediaDecodeTime: 0, segmentStartTime: 5 },
|
||||
},
|
||||
sel
|
||||
)
|
||||
).toEqual({ video: 0, audio: 0 });
|
||||
});
|
||||
});
|
||||
|
||||
describe('derivePerTypeStartMediaTime', () => {
|
||||
@@ -103,4 +144,16 @@ describe('derivePerTypeStartMediaTime', () => {
|
||||
audio: undefined,
|
||||
});
|
||||
});
|
||||
|
||||
it('snaps a below-threshold origin to 0 independently per type', () => {
|
||||
expect(
|
||||
derivePerTypeStartMediaTime(
|
||||
{
|
||||
video: { timescale: 90000, baseMediaDecodeTime: 45000, segmentStartTime: 0 }, // 0.5s → 0
|
||||
audio: { timescale: 48000, baseMediaDecodeTime: 48000 * 60, segmentStartTime: 0 }, // 60s → 60
|
||||
},
|
||||
{}
|
||||
)
|
||||
).toEqual({ video: 0, audio: 60 });
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user