Home Reference Source

src/controller/fragment-finders.ts

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