Home Reference Source

src/controller/fragment-finders.ts

  1. import BinarySearch from '../utils/binary-search';
  2. import Fragment from '../loader/fragment';
  3.  
  4. /**
  5. * Returns first fragment whose endPdt value exceeds the given PDT.
  6. * @param {Array<Fragment>} fragments - The array of candidate fragments
  7. * @param {number|null} [PDTValue = null] - The PDT value which must be exceeded
  8. * @param {number} [maxFragLookUpTolerance = 0] - The amount of time that a fragment's start/end can be within in order to be considered contiguous
  9. * @returns {*|null} fragment - The best matching fragment
  10. */
  11. export function findFragmentByPDT (fragments: Array<Fragment>, PDTValue: number | null, maxFragLookUpTolerance: number): Fragment | null {
  12. if (PDTValue === null || !Array.isArray(fragments) || !fragments.length || !Number.isFinite(PDTValue)) {
  13. return null;
  14. }
  15.  
  16. // if less than start
  17. const startPDT = fragments[0].programDateTime;
  18. if (PDTValue < (startPDT || 0)) {
  19. return null;
  20. }
  21.  
  22. const endPDT = fragments[fragments.length - 1].endProgramDateTime;
  23. if (PDTValue >= (endPDT || 0)) {
  24. return null;
  25. }
  26.  
  27. maxFragLookUpTolerance = maxFragLookUpTolerance || 0;
  28. for (let seg = 0; seg < fragments.length; ++seg) {
  29. let frag = fragments[seg];
  30. if (pdtWithinToleranceTest(PDTValue, maxFragLookUpTolerance, frag)) {
  31. return frag;
  32. }
  33. }
  34.  
  35. return null;
  36. }
  37.  
  38. /**
  39. * Finds a fragment based on the SN of the previous fragment; or based on the needs of the current buffer.
  40. * This method compensates for small buffer gaps by applying a tolerance to the start of any candidate fragment, thus
  41. * breaking any traps which would cause the same fragment to be continuously selected within a small range.
  42. * @param {*} fragPrevious - The last frag successfully appended
  43. * @param {Array<Fragment>} fragments - The array of candidate fragments
  44. * @param {number} [bufferEnd = 0] - The end of the contiguous buffered range the playhead is currently within
  45. * @param {number} maxFragLookUpTolerance - The amount of time that a fragment's start/end can be within in order to be considered contiguous
  46. * @returns {*} foundFrag - The best matching fragment
  47. */
  48. export function findFragmentByPTS (fragPrevious: Fragment, fragments: Array<Fragment>, bufferEnd: number = 0, maxFragLookUpTolerance: number = 0): Fragment | null {
  49. const fragNext = fragPrevious ? fragments[fragPrevious.sn as number - (fragments[0].sn as number) + 1] : null;
  50. // Prefer the next fragment if it's within tolerance
  51. if (fragNext && !fragmentWithinToleranceTest(bufferEnd, maxFragLookUpTolerance, fragNext)) {
  52. return fragNext;
  53. }
  54. return BinarySearch.search(fragments, fragmentWithinToleranceTest.bind(null, bufferEnd, maxFragLookUpTolerance));
  55. }
  56.  
  57. /**
  58. * The test function used by the findFragmentBySn's BinarySearch to look for the best match to the current buffer conditions.
  59. * @param {*} candidate - The fragment to test
  60. * @param {number} [bufferEnd = 0] - The end of the current buffered range the playhead is currently within
  61. * @param {number} [maxFragLookUpTolerance = 0] - The amount of time that a fragment's start can be within in order to be considered contiguous
  62. * @returns {number} - 0 if it matches, 1 if too low, -1 if too high
  63. */
  64. export function fragmentWithinToleranceTest (bufferEnd = 0, maxFragLookUpTolerance = 0, candidate: Fragment) {
  65. // offset should be within fragment boundary - config.maxFragLookUpTolerance
  66. // this is to cope with situations like
  67. // bufferEnd = 9.991
  68. // frag[Ø] : [0,10]
  69. // frag[1] : [10,20]
  70. // bufferEnd is within frag[0] range ... although what we are expecting is to return frag[1] here
  71. // frag start frag start+duration
  72. // |-----------------------------|
  73. // <---> <--->
  74. // ...--------><-----------------------------><---------....
  75. // previous frag matching fragment next frag
  76. // return -1 return 0 return 1
  77. // logger.log(`level/sn/start/end/bufEnd:${level}/${candidate.sn}/${candidate.start}/${(candidate.start+candidate.duration)}/${bufferEnd}`);
  78. // Set the lookup tolerance to be small enough to detect the current segment - ensures we don't skip over very small segments
  79. let candidateLookupTolerance = Math.min(maxFragLookUpTolerance, candidate.duration + (candidate.deltaPTS ? candidate.deltaPTS : 0));
  80. if (candidate.start + candidate.duration - candidateLookupTolerance <= bufferEnd) {
  81. return 1;
  82. } else if (candidate.start - candidateLookupTolerance > bufferEnd && candidate.start) {
  83. // if maxFragLookUpTolerance will have negative value then don't return -1 for first element
  84. return -1;
  85. }
  86.  
  87. return 0;
  88. }
  89.  
  90. /**
  91. * The test function used by the findFragmentByPdt's BinarySearch to look for the best match to the current buffer conditions.
  92. * This function tests the candidate's program date time values, as represented in Unix time
  93. * @param {*} candidate - The fragment to test
  94. * @param {number} [pdtBufferEnd = 0] - The Unix time representing the end of the current buffered range
  95. * @param {number} [maxFragLookUpTolerance = 0] - The amount of time that a fragment's start can be within in order to be considered contiguous
  96. * @returns {boolean} True if contiguous, false otherwise
  97. */
  98. export function pdtWithinToleranceTest (pdtBufferEnd: number, maxFragLookUpTolerance: number, candidate: Fragment): boolean {
  99. let candidateLookupTolerance = Math.min(maxFragLookUpTolerance, candidate.duration + (candidate.deltaPTS ? candidate.deltaPTS : 0)) * 1000;
  100.  
  101. // endProgramDateTime can be null, default to zero
  102. const endProgramDateTime = candidate.endProgramDateTime || 0;
  103. return endProgramDateTime - candidateLookupTolerance > pdtBufferEnd;
  104. }