mirror of
https://github.com/zoriya/v10.git
synced 2026-08-16 02:45:09 +00:00
refactor(spf): extract resolveLiveLatency out of the engine closure
The HLS live-latency bridge was defined inside createSimpleHlsEngine's closure despite capturing nothing — a fresh allocation per engine, and pure HLS logic stranded where it can't be tested. Move it to media/hls/reload-policy.ts as a pure resolveLiveLatency(presentation, trackId) next to liveLatencyFor; the engine injects the import. Adds direct coverage for the bridge (previously only stubbed via seek-to-live-edge's injected resolver). 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
de2e1f823f
commit
bfe12eedf9
@@ -1,4 +1,11 @@
|
||||
import { getMediaPlaylistMetadata, type ResolvedTrack } from '../types';
|
||||
import {
|
||||
getMediaPlaylistMetadata,
|
||||
isResolvedPresentation,
|
||||
isResolvedTrack,
|
||||
type MaybeResolvedPresentation,
|
||||
type ResolvedTrack,
|
||||
} from '../types';
|
||||
import { findTrackById } from '../utils/tracks';
|
||||
|
||||
/** Reload cadence when a playlist carries no usable target duration. */
|
||||
const FALLBACK_TARGET_DURATION = 6;
|
||||
@@ -56,3 +63,18 @@ export function mediaPlaylistReloadDelay(current: ResolvedTrack, previous: Resol
|
||||
export function liveLatencyFor(track: ResolvedTrack): number {
|
||||
return HOLD_BACK_TARGET_MULTIPLIER * targetDurationOf(track);
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve the target live latency for a presentation's timeline-bearing track —
|
||||
* the HLS engine injects this as `seekToLiveEdge`'s format-neutral
|
||||
* `resolveLiveLatency` seam. `0` when there is no resolved track to read (the
|
||||
* behavior then seeks straight to the edge).
|
||||
*/
|
||||
export function resolveLiveLatency(
|
||||
presentation: MaybeResolvedPresentation | undefined,
|
||||
trackId: string | undefined
|
||||
): number {
|
||||
if (!isResolvedPresentation(presentation) || !trackId) return 0;
|
||||
const track = findTrackById(presentation, trackId);
|
||||
return track && isResolvedTrack(track) ? liveLatencyFor(track) : 0;
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import { MEDIA_PLAYLIST_METADATA_KEY, type ResolvedTrack } from '../../types';
|
||||
import { liveLatencyFor, mediaPlaylistReloadDelay } from '../reload-policy';
|
||||
import { type MaybeResolvedPresentation, MEDIA_PLAYLIST_METADATA_KEY, type ResolvedTrack } from '../../types';
|
||||
import { liveLatencyFor, mediaPlaylistReloadDelay, resolveLiveLatency } from '../reload-policy';
|
||||
|
||||
/** Minimal resolved-track stand-in carrying only what the policy reads. */
|
||||
function track(opts: {
|
||||
@@ -58,3 +58,29 @@ describe('liveLatencyFor', () => {
|
||||
expect(liveLatencyFor(track({ targetDuration: 0 }))).toBe(18);
|
||||
});
|
||||
});
|
||||
|
||||
describe('resolveLiveLatency', () => {
|
||||
/** Minimal presentation wrapping one video track, enough for findTrackById + liveLatencyFor. */
|
||||
function presentation(targetDuration: number): MaybeResolvedPresentation {
|
||||
const video = {
|
||||
id: 'v-1',
|
||||
segments: [{ id: 's0', url: 's0.m4s', duration: 2, startTime: 0 }],
|
||||
metadata: { [MEDIA_PLAYLIST_METADATA_KEY]: { targetDuration, mediaSequence: 0, endList: false } },
|
||||
};
|
||||
return {
|
||||
id: 'p-1',
|
||||
url: 'master.m3u8',
|
||||
selectionSets: [{ switchingSets: [{ tracks: [video] }] }],
|
||||
} as unknown as MaybeResolvedPresentation;
|
||||
}
|
||||
|
||||
it('returns the timeline-bearing track latency (3× target duration)', () => {
|
||||
expect(resolveLiveLatency(presentation(2), 'v-1')).toBe(6);
|
||||
});
|
||||
|
||||
it('returns 0 when the presentation is unresolved, or the track id is absent / unknown', () => {
|
||||
expect(resolveLiveLatency(undefined, 'v-1')).toBe(0);
|
||||
expect(resolveLiveLatency(presentation(2), undefined)).toBe(0);
|
||||
expect(resolveLiveLatency(presentation(2), 'missing')).toBe(0);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -20,20 +20,17 @@ import {
|
||||
removeAllSubtitlesTracksFromMedia,
|
||||
} from '../../../media/dom/text/text-track-slots';
|
||||
import { parseMultivariantPlaylist } from '../../../media/hls/parse-multivariant';
|
||||
import { liveLatencyFor, mediaPlaylistReloadDelay } from '../../../media/hls/reload-policy';
|
||||
import {
|
||||
type AudioTrack,
|
||||
type CanPlayTrack,
|
||||
isResolvedPresentation,
|
||||
isResolvedTrack,
|
||||
type MaybeResolvedPresentation,
|
||||
type ResolvedTrack,
|
||||
type TextTrack,
|
||||
type VideoTrack,
|
||||
import { mediaPlaylistReloadDelay, resolveLiveLatency } from '../../../media/hls/reload-policy';
|
||||
import type {
|
||||
AudioTrack,
|
||||
CanPlayTrack,
|
||||
MaybeResolvedPresentation,
|
||||
ResolvedTrack,
|
||||
TextTrack,
|
||||
VideoTrack,
|
||||
} from '../../../media/types';
|
||||
import type { GetCdnId } from '../../../media/utils/cdn';
|
||||
import { getResolvedSelectedTrackDuration } from '../../../media/utils/track-selection';
|
||||
import { findTrackById } from '../../../media/utils/tracks';
|
||||
import type { BandwidthConfig, BandwidthState } from '../../../network/bandwidth-estimator';
|
||||
import type { SegmentLoaderActor } from '../../actors/dom/segment-loader';
|
||||
import type { SourceBufferActor } from '../../actors/dom/source-buffer';
|
||||
@@ -341,19 +338,11 @@ export function createSimpleHlsEngine(
|
||||
return bufferedAnchorFor(context.segments, context.bufferedRanges);
|
||||
});
|
||||
|
||||
// Format-specific live latency for `seekToLiveEdge`: look up the
|
||||
// timeline-bearing track and apply the HLS HOLD-BACK rule. The behavior
|
||||
// consumes this as a neutral `resolveLiveLatency` seam (a DASH engine would
|
||||
// inject `suggestedPresentationDelay`).
|
||||
const resolveLiveLatency = (presentation: MaybeResolvedPresentation | undefined, trackId: string | undefined) => {
|
||||
if (!isResolvedPresentation(presentation) || !trackId) return 0;
|
||||
const track = findTrackById(presentation, trackId);
|
||||
return track && isResolvedTrack(track) ? liveLatencyFor(track) : 0;
|
||||
};
|
||||
|
||||
const finalConfig = {
|
||||
...config,
|
||||
resolveBufferedAnchor,
|
||||
// Format-neutral live-latency seam for `seekToLiveEdge` — the HLS resolver
|
||||
// (HOLD-BACK); a DASH engine would inject `suggestedPresentationDelay`.
|
||||
resolveLiveLatency,
|
||||
canPlayTrack: config.canPlayTrack ?? canPlayTrack,
|
||||
// The resolve* loaders' RecurringRunner re-runs on this `reschedule`: the pure
|
||||
|
||||
Reference in New Issue
Block a user