# All files / src/controller fragment-finders.js

93.94% Statements 31/33
75.61% Branches 31/41
100% Functions 4/4
93.94% Lines 31/33

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116

    7x
    7x
    6x
    6x
    5x

    7x

    14x
    4x

    10x

    10x
    1x

    9x

    9x
    1x

    8x
    21x
    21x
    8x

    7x
    7x
    7x

    7x
    4x

    3x

    7x

    20x
    20x
    6x
    14x

    3x

    11x

     import BinarySearch from '../utils/binary-search';

    /**
     * Calculates the PDT of the next load position.
     * bufferEnd in this function is usually the position of the playhead.
     * @param {number} [start = 0] - The PTS of the first fragment within the level
     * @param {number} [bufferEnd = 0] - The end of the contiguous buffered range the playhead is currently within
     * @param {*} levelDetails - An object containing the parsed and computed properties of the currently playing level
     * @returns {number} nextPdt - The computed PDT
     */
    export function calculateNextPDT (start = 0, bufferEnd = 0, levelDetails) {
      let pdt = 0;
      if (levelDetails.programDateTime) {
        const parsedDateInt = Date.parse(levelDetails.programDateTime);
        if (!isNaN(parsedDateInt)) {
          pdt = (bufferEnd * 1000) + parsedDateInt - (1000 * start);
        }
      }
      return pdt;
    }

    /**
     * Finds the 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
     * @returns {*|null} fragment - The best matching fragment
     */
    export function findFragmentByPDT (fragments, PDTValue = null) {
      if (!Array.isArray(fragments) || !fragments.length || PDTValue === null) {
        return null;
      }

      // if less than start
      let firstSegment = fragments[0];

      if (PDTValue < firstSegment.pdt) {
        return null;
      }

      let lastSegment = fragments[fragments.length - 1];

      if (PDTValue >= lastSegment.endPdt) {
        return null;
      }

      for (let seg = 0; seg < fragments.length; ++seg) {
        let frag = fragments[seg];
        if (PDTValue < frag.endPdt) {
          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<Fragment>} fragments - The array of candidate fragments
     * @param {number} [bufferEnd = 0] - The end of the contiguous buffered range the playhead is currently within
     * @param {number} [end = 0] - The computed end time of the stream
     * @param {number} maxFragLookUpTolerance - The amount of time that a fragment's start can be within in order to be considered contiguous
     * @returns {*} foundFrag - The best matching fragment
     */
    export function findFragmentBySN (fragPrevious, fragments, bufferEnd = 0, end = 0, maxFragLookUpTolerance = 0) {
      let foundFrag;
      const fragNext = fragPrevious ? fragments[fragPrevious.sn - fragments[0].sn + 1] : null;
      Eif (bufferEnd < end) {
        Iif (bufferEnd > end - maxFragLookUpTolerance) {
          maxFragLookUpTolerance = 0;
        }

        // Prefer the next fragment if it's within tolerance
        if (fragNext && !fragmentWithinToleranceTest(bufferEnd, maxFragLookUpTolerance, fragNext)) {
          foundFrag = fragNext;
        } else {
          foundFrag = BinarySearch.search(fragments, fragmentWithinToleranceTest.bind(null, bufferEnd, maxFragLookUpTolerance));
        }
      }
      return foundFrag;
    }

    /**
     * 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) {
      // 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
      let 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;
    }