download
raw
6 kB
import { adjustSliding } from './level-helper';
import { logger } from './logger';
import type { Fragment } from '../loader/fragment';
import type { LevelDetails } from '../loader/level-details';
export function findFirstFragWithCC(
fragments: Fragment[],
cc: number,
): Fragment | null {
for (let i = 0, len = fragments.length; i < len; i++) {
if (fragments[i]?.cc === cc) {
return fragments[i];
}
}
return null;
}
export function shouldAlignOnDiscontinuities(
refDetails: LevelDetails | undefined,
details: LevelDetails,
): refDetails is LevelDetails & boolean {
if (refDetails) {
if (
details.startCC < refDetails.endCC &&
details.endCC > refDetails.startCC
) {
return true;
}
}
return false;
}
function adjustFragmentStart(frag: Fragment, sliding: number) {
const start = frag.start + sliding;
frag.startPTS = start;
frag.setStart(start);
frag.endPTS = start + frag.duration;
}
export function adjustSlidingStart(sliding: number, details: LevelDetails) {
// Update segments
const fragments = details.fragments;
for (let i = 0, len = fragments.length; i < len; i++) {
adjustFragmentStart(fragments[i], sliding);
}
// Update LL-HLS parts at the end of the playlist
if (details.fragmentHint) {
adjustFragmentStart(details.fragmentHint, sliding);
}
details.alignedSliding = true;
}
/**
* Using the parameters of the last level, this function computes PTS' of the new fragments so that they form a
* contiguous stream with the last fragments.
* The PTS of a fragment lets Hls.js know where it fits into a stream - by knowing every PTS, we know which fragment to
* download at any given time. PTS is normally computed when the fragment is demuxed, so taking this step saves us time
* and an extra download.
* @param lastLevel
* @param details
*/
export function alignStream(
switchDetails: LevelDetails | undefined,
details: LevelDetails,
) {
if (!switchDetails) {
return;
}
alignDiscontinuities(details, switchDetails);
if (!details.alignedSliding) {
// If the PTS wasn't figured out via discontinuity sequence that means there was no CC increase within the level.
// Aligning via Program Date Time should therefore be reliable, since PDT should be the same within the same
// discontinuity sequence.
alignMediaPlaylistByPDT(details, switchDetails);
}
if (!details.alignedSliding && !details.skippedSegments) {
// Try to align on sn so that we pick a better start fragment.
// Do not perform this on playlists with delta updates as this is only to align levels on switch
// and adjustSliding only adjusts fragments after skippedSegments.
adjustSliding(switchDetails, details, false);
}
}
/**
* Ajust the start of fragments in `details` by the difference in time between fragments of the latest
* shared discontinuity sequence change.
* @param lastLevel - The details of the last loaded level
* @param details - The details of the new level
*/
export function alignDiscontinuities(
details: LevelDetails,
refDetails: LevelDetails | undefined,
) {
if (!shouldAlignOnDiscontinuities(refDetails, details)) {
return;
}
const targetCC = Math.min(refDetails.endCC, details.endCC);
const refFrag = findFirstFragWithCC(refDetails.fragments, targetCC);
const frag = findFirstFragWithCC(details.fragments, targetCC);
if (!refFrag || !frag) {
return;
}
logger.log(`Aligning playlist at start of dicontinuity sequence ${targetCC}`);
const delta = refFrag.start - frag.start;
adjustSlidingStart(delta, details);
}
/**
* Ensures appropriate time-alignment between renditions based on PDT.
* This function assumes the timelines represented in `refDetails` are accurate, including the PDTs
* for the last discontinuity sequence number shared by both playlists when present,
* and uses the "wallclock"/PDT timeline as a cross-reference to `details`, adjusting the presentation
* times/timelines of `details` accordingly.
* Given the asynchronous nature of fetches and initial loads of live `main` and audio/subtitle tracks,
* the primary purpose of this function is to ensure the "local timelines" of audio/subtitle tracks
* are aligned to the main/video timeline, using PDT as the cross-reference/"anchor" that should
* be consistent across playlists, per the HLS spec.
* @param details - The details of the rendition you'd like to time-align (e.g. an audio rendition).
* @param refDetails - The details of the reference rendition with start and PDT times for alignment.
*/
export function alignMediaPlaylistByPDT(
details: LevelDetails,
refDetails: LevelDetails,
) {
if (!details.hasProgramDateTime || !refDetails.hasProgramDateTime) {
return;
}
const fragments = details.fragments;
const refFragments = refDetails.fragments;
if (!fragments.length || !refFragments.length) {
return;
}
// Calculate a delta to apply to all fragments according to the delta in PDT times and start times
// of a fragment in the reference details, and a fragment in the target details of the same discontinuity.
// If a fragment of the same discontinuity was not found use the middle fragment of both.
let refFrag: Fragment | null | undefined;
let frag: Fragment | null | undefined;
const targetCC = Math.min(refDetails.endCC, details.endCC);
if (refDetails.startCC < targetCC && details.startCC < targetCC) {
refFrag = findFirstFragWithCC(refFragments, targetCC);
frag = findFirstFragWithCC(fragments, targetCC);
}
if (!refFrag || !frag) {
refFrag = refFragments[Math.floor(refFragments.length / 2)];
frag =
findFirstFragWithCC(fragments, refFrag.cc) ||
fragments[Math.floor(fragments.length / 2)];
}
const refPDT = refFrag.programDateTime;
const targetPDT = frag.programDateTime;
if (!refPDT || !targetPDT) {
return;
}
const delta = (targetPDT - refPDT) / 1000 - (frag.start - refFrag.start);
adjustSlidingStart(delta, details);
}

Xet Storage Details

Size:
6 kB
·
Xet hash:
ef7d5be3817d7daefd4edad3a918bc2ab28ba7bde0bb8c4b5fd8710a3b3f9477

Xet efficiently stores files, intelligently splitting them into unique chunks and accelerating uploads and downloads. More info.