src/controller/fragment-finders.ts
import BinarySearch from '../utils/binary-search';
import { Fragment } from '../loader/fragment';
/**
* Returns first fragment whose endPdt value exceeds the given PDT.
* @param {Array<Fragment>} fragments - The array of candidate fragments
* @param {number|null} [PDTValue = null] - The PDT value which must be exceeded
* @param {number} [maxFragLookUpTolerance = 0] - The amount of time that a fragment's start/end can be within in order to be considered contiguous
* @returns {*|null} fragment - The best matching fragment
*/
export function findFragmentByPDT(
fragments: Array<Fragment>,
PDTValue: number | null,
maxFragLookUpTolerance: number
): Fragment | null {
if (
PDTValue === null ||
!Array.isArray(fragments) ||
!fragments.length ||
!Number.isFinite(PDTValue)
) {
return null;
}
// if less than start
const startPDT = fragments[0].programDateTime;
if (PDTValue < (startPDT || 0)) {
return null;
}
const endPDT = fragments[fragments.length - 1].endProgramDateTime;
if (PDTValue >= (endPDT || 0)) {
return null;
}
maxFragLookUpTolerance = maxFragLookUpTolerance || 0;
for (let seg = 0; seg < fragments.length; ++seg) {
const frag = fragments[seg];
if (pdtWithinToleranceTest(PDTValue, maxFragLookUpTolerance, frag)) {
return frag;
}
}
return null;
}
/**
* Finds a fragment based on the SN of the previous fragment; or based on the needs of the current buffer.
* This method compensates for small buffer gaps by applying a tolerance to the start of any candidate fragment, thus
* breaking any traps which would cause the same fragment to be continuously selected within a small range.
* @param {*} fragPrevious - The last frag successfully appended
* @param {Array} fragments - The array of candidate fragments
* @param {number} [bufferEnd = 0] - The end of the contiguous buffered range the playhead is currently within
* @param {number} maxFragLookUpTolerance - The amount of time that a fragment's start/end can be within in order to be considered contiguous
* @returns {*} foundFrag - The best matching fragment
*/
export function findFragmentByPTS(
fragPrevious: Fragment | null,
fragments: Array<Fragment>,
bufferEnd: number = 0,
maxFragLookUpTolerance: number = 0
): Fragment | null {
let fragNext: Fragment | null = null;
if (fragPrevious) {
fragNext =
fragments[(fragPrevious.sn as number) - (fragments[0].sn as number) + 1];
} else if (bufferEnd === 0 && fragments[0].start === 0) {
fragNext = fragments[0];
}
// Prefer the next fragment if it's within tolerance
if (
fragNext &&
fragmentWithinToleranceTest(bufferEnd, maxFragLookUpTolerance, fragNext) ===
0
) {
return fragNext;
}
// We might be seeking past the tolerance so find the best match
const foundFragment = BinarySearch.search(
fragments,
fragmentWithinToleranceTest.bind(null, bufferEnd, maxFragLookUpTolerance)
);
if (foundFragment) {
return foundFragment;
}
// If no match was found return the next fragment after fragPrevious, or null
return fragNext;
}
/**
* The test function used by the findFragmentBySn's BinarySearch to look for the best match to the current buffer conditions.
* @param {*} candidate - The fragment to test
* @param {number} [bufferEnd = 0] - The end of the current buffered range the playhead is currently within
* @param {number} [maxFragLookUpTolerance = 0] - The amount of time that a fragment's start can be within in order to be considered contiguous
* @returns {number} - 0 if it matches, 1 if too low, -1 if too high
*/
export function fragmentWithinToleranceTest(
bufferEnd = 0,
maxFragLookUpTolerance = 0,
candidate: Fragment
) {
// offset should be within fragment boundary - config.maxFragLookUpTolerance
// this is to cope with situations like
// bufferEnd = 9.991
// frag[Ø] : [0,10]
// frag[1] : [10,20]
// bufferEnd is within frag[0] range ... although what we are expecting is to return frag[1] here
// frag start frag start+duration
// |-----------------------------|
// <---> <--->
// ...--------><-----------------------------><---------....
// previous frag matching fragment next frag
// return -1 return 0 return 1
// logger.log(`level/sn/start/end/bufEnd:${level}/${candidate.sn}/${candidate.start}/${(candidate.start+candidate.duration)}/${bufferEnd}`);
// Set the lookup tolerance to be small enough to detect the current segment - ensures we don't skip over very small segments
const candidateLookupTolerance = Math.min(
maxFragLookUpTolerance,
candidate.duration + (candidate.deltaPTS ? candidate.deltaPTS : 0)
);
if (
candidate.start + candidate.duration - candidateLookupTolerance <=
bufferEnd
) {
return 1;
} else if (
candidate.start - candidateLookupTolerance > bufferEnd &&
candidate.start
) {
// if maxFragLookUpTolerance will have negative value then don't return -1 for first element
return -1;
}
return 0;
}
/**
* The test function used by the findFragmentByPdt's BinarySearch to look for the best match to the current buffer conditions.
* This function tests the candidate's program date time values, as represented in Unix time
* @param {*} candidate - The fragment to test
* @param {number} [pdtBufferEnd = 0] - The Unix time representing the end of the current buffered range
* @param {number} [maxFragLookUpTolerance = 0] - The amount of time that a fragment's start can be within in order to be considered contiguous
* @returns {boolean} True if contiguous, false otherwise
*/
export function pdtWithinToleranceTest(
pdtBufferEnd: number,
maxFragLookUpTolerance: number,
candidate: Fragment
): boolean {
const candidateLookupTolerance =
Math.min(
maxFragLookUpTolerance,
candidate.duration + (candidate.deltaPTS ? candidate.deltaPTS : 0)
) * 1000;
// endProgramDateTime can be null, default to zero
const endProgramDateTime = candidate.endProgramDateTime || 0;
return endProgramDateTime - candidateLookupTolerance > pdtBufferEnd;
}
export function findFragWithCC(
fragments: Fragment[],
cc: number
): Fragment | null {
return BinarySearch.search(fragments, (candidate) => {
if (candidate.cc < cc) {
return 1;
} else if (candidate.cc > cc) {
return -1;
} else {
return 0;
}
});
}