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. const 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} 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 | null, fragments: Array<Fragment>, bufferEnd: number = 0, maxFragLookUpTolerance: number = 0): Fragment | null {
  49. let fragNext: Fragment | null = null;
  50. if (fragPrevious) {
  51. fragNext = fragments[fragPrevious.sn as number - (fragments[0].sn as number) + 1];
  52. } else if (bufferEnd === 0 && fragments[0].start === 0) {
  53. fragNext = fragments[0];
  54. }
  55. // Prefer the next fragment if it's within tolerance
  56. if (fragNext && fragmentWithinToleranceTest(bufferEnd, maxFragLookUpTolerance, fragNext) === 0) {
  57. return fragNext;
  58. }
  59. // We might be seeking past the tolerance so find the best match
  60. const foundFragment = BinarySearch.search(fragments, fragmentWithinToleranceTest.bind(null, bufferEnd, maxFragLookUpTolerance));
  61. if (foundFragment) {
  62. return foundFragment;
  63. }
  64. // If no match was found return the next fragment after fragPrevious, or null
  65. return fragNext;
  66. }
  67.  
  68. /**
  69. * The test function used by the findFragmentBySn's BinarySearch to look for the best match to the current buffer conditions.
  70. * @param {*} candidate - The fragment to test
  71. * @param {number} [bufferEnd = 0] - The end of the current buffered range the playhead is currently within
  72. * @param {number} [maxFragLookUpTolerance = 0] - The amount of time that a fragment's start can be within in order to be considered contiguous
  73. * @returns {number} - 0 if it matches, 1 if too low, -1 if too high
  74. */
  75. export function fragmentWithinToleranceTest (bufferEnd = 0, maxFragLookUpTolerance = 0, candidate: Fragment) {
  76. // offset should be within fragment boundary - config.maxFragLookUpTolerance
  77. // this is to cope with situations like
  78. // bufferEnd = 9.991
  79. // frag[Ø] : [0,10]
  80. // frag[1] : [10,20]
  81. // bufferEnd is within frag[0] range ... although what we are expecting is to return frag[1] here
  82. // frag start frag start+duration
  83. // |-----------------------------|
  84. // <---> <--->
  85. // ...--------><-----------------------------><---------....
  86. // previous frag matching fragment next frag
  87. // return -1 return 0 return 1
  88. // logger.log(`level/sn/start/end/bufEnd:${level}/${candidate.sn}/${candidate.start}/${(candidate.start+candidate.duration)}/${bufferEnd}`);
  89. // Set the lookup tolerance to be small enough to detect the current segment - ensures we don't skip over very small segments
  90. const candidateLookupTolerance = Math.min(maxFragLookUpTolerance, candidate.duration + (candidate.deltaPTS ? candidate.deltaPTS : 0));
  91. if (candidate.start + candidate.duration - candidateLookupTolerance <= bufferEnd) {
  92. return 1;
  93. } else if (candidate.start - candidateLookupTolerance > bufferEnd && candidate.start) {
  94. // if maxFragLookUpTolerance will have negative value then don't return -1 for first element
  95. return -1;
  96. }
  97.  
  98. return 0;
  99. }
  100.  
  101. /**
  102. * The test function used by the findFragmentByPdt's BinarySearch to look for the best match to the current buffer conditions.
  103. * This function tests the candidate's program date time values, as represented in Unix time
  104. * @param {*} candidate - The fragment to test
  105. * @param {number} [pdtBufferEnd = 0] - The Unix time representing the end of the current buffered range
  106. * @param {number} [maxFragLookUpTolerance = 0] - The amount of time that a fragment's start can be within in order to be considered contiguous
  107. * @returns {boolean} True if contiguous, false otherwise
  108. */
  109. export function pdtWithinToleranceTest (pdtBufferEnd: number, maxFragLookUpTolerance: number, candidate: Fragment): boolean {
  110. const candidateLookupTolerance = Math.min(maxFragLookUpTolerance, candidate.duration + (candidate.deltaPTS ? candidate.deltaPTS : 0)) * 1000;
  111.  
  112. // endProgramDateTime can be null, default to zero
  113. const endProgramDateTime = candidate.endProgramDateTime || 0;
  114. return endProgramDateTime - candidateLookupTolerance > pdtBufferEnd;
  115. }
  116.  
  117. export function findFragWithCC (fragments, CC): Fragment | null {
  118. return BinarySearch.search(fragments, (candidate) => {
  119. if (candidate.cc < CC) {
  120. return 1;
  121. } else if (candidate.cc > CC) {
  122. return -1;
  123. } else {
  124. return 0;
  125. }
  126. });
  127. }