/// <summary>
        /// return the fragment information for the first fragment in the FRT whose fragment number
        /// is greater than or equal to fragment id. special cases:
        ///
        /// if fragmentId is in a gap, the first fragment after the gap will be returned.
        /// if fragmentId is in a skip, the first fragment after the skip will be returned.
        /// if fragmentId is before the first fragment-duration-pair, the first fragment will be returned.
        /// if fragmentId is after the last fragment-duration-pair, it will be assumed to exist.
        ///       (in other words, the live point is ignored).
        ///
        /// if there are no valid entries in the FRT, returns null. this is the only situation that returns null.
        /// </summary>
        public FragmentAccessInformation getFragmentWithIdGreq(uint fragmentId)
        {
            FragmentDurationPair desiredFdp = null;
            uint desiredFragmentId          = 0;

            forEachInterval(delegate(FragmentDurationPair fdp, bool isLast, uint startFragmentId, uint endFragmentId, uint startTime, uint endTime) {
                if (fragmentId < startFragmentId)
                {
                    // before the given interval
                    desiredFdp        = fdp;
                    desiredFragmentId = startFragmentId;
                    return(false); // stop iterating
                }
                else if (isLast)
                {
                    // catch all in the last entry
                    desiredFdp        = fdp;
                    desiredFragmentId = fragmentId;
                    return(false);
                }
                else if (fragmentId < endFragmentId)
                {
                    // between the start and end of this interval
                    desiredFdp        = fdp;
                    desiredFragmentId = fragmentId;
                    return(false); // stop iterating
                }
                else
                {
                    // beyond this interval, but not the last entry
                    return(true); // keep iterating
                }
            });

            if (desiredFdp == null)
            {
                // no fragment entries case
                return(null);
            }

            if (desiredFragmentId < desiredFdp.firstFragment)
            {
                // probably won't ever hit this
                // just make sure that we're before the start
                desiredFragmentId = desiredFdp.firstFragment;
            }

            FragmentAccessInformation fai = new FragmentAccessInformation();

            fai.fragId          = desiredFragmentId;
            fai.fragDuration    = desiredFdp.duration;
            fai.fragmentEndTime = (uint)desiredFdp.durationAccrued + (desiredFragmentId - desiredFdp.firstFragment + 1) * desiredFdp.duration;
            return(fai);
        }
        /// <summary>
        /// return the fragment information for the first fragment in the FRT that contains a time
        /// greater than or equal to fragment time. special cases:
        ///
        /// if time is in a gap, the first fragment after the gap will be returned.
        /// if time is in a skip, the first fragment after the skip will be returned.
        /// if time is before the first fragment-duration-pair, the first fragment will be returned.
        /// if time is after the last fragment-duration-pair, it will be assumed to exist.
        ///       (in other words, the live point is ignored).
        ///
        /// if there are no valid entries in the FRT, returns null. this is the only situation that returns null.
        /// </summary>
        public FragmentAccessInformation getFragmentWithTimeGreq(uint fragmentTime)
        {
            FragmentDurationPair desiredFdp = null;
            uint desiredFragmentStartTime   = 0;

            forEachInterval(delegate(FragmentDurationPair fdp, bool isLast, uint startFragmentId, uint endFragmentId, uint startTime, uint endTime) {
                if (fragmentTime < startTime)
                {
                    // before the given interval
                    desiredFdp = fdp;
                    desiredFragmentStartTime = startTime;
                    return(false); // stop iterating
                }
                else if (isLast)
                {
                    // catch all in the last entry
                    desiredFdp = fdp;
                    desiredFragmentStartTime = fragmentTime;
                    return(false);
                }
                else if (fragmentTime < endTime)
                {
                    // between the start and end of this interval
                    desiredFdp = fdp;
                    desiredFragmentStartTime = fragmentTime;
                    return(false); // stop iterating
                }
                else
                {
                    // beyond this interval, but not the last entry
                    return(true); // keep iterating
                }
            });

            if (desiredFdp == null)
            {
                // no fragment entries case
                return(null);
            }

            uint desiredFragmentId        = calculateFragmentId(desiredFdp, desiredFragmentStartTime);
            FragmentAccessInformation fai = new FragmentAccessInformation();

            fai.fragId          = desiredFragmentId;
            fai.fragDuration    = desiredFdp.duration;
            fai.fragmentEndTime = (uint)desiredFdp.durationAccrued + (desiredFragmentId - desiredFdp.firstFragment + 1) * desiredFdp.duration;
            return(fai);
        }
        private FragmentAccessInformation getNextValidFragment(int startIdx)
        {
            FragmentAccessInformation fai = null;

            for (int i = startIdx; i < fragmentDurationPairs.Count; i++)
            {
                FragmentDurationPair fdp = fragmentDurationPairs[i];
                if (fdp.duration > 0)
                {
                    fai                 = new FragmentAccessInformation();
                    fai.fragId          = fdp.firstFragment;
                    fai.fragDuration    = fdp.duration;
                    fai.fragmentEndTime = (uint)fdp.durationAccrued + fdp.duration;
                    break;
                }
            }
            return(fai);
        }
        /// <summary>
        /// Given a fragment id, check whether the current fragment is valid or a discontinuity.
        /// If the latter, skip to the nearest fragment and return the new fragment id.
        ///
        /// return the Id of the fragment that is valid.
        /// </summary>
        public FragmentAccessInformation validateFragment(uint fragId, ulong totalDuration, bool live = false)
        {
            int size = fragmentDurationPairs.Count - 1;
            FragmentAccessInformation fai = null;
            uint timeResidue, timeDistance, fragStartTime;

            for (int i = 0; i < size; i++)
            {
                FragmentDurationPair curFdp  = fragmentDurationPairs[i];
                FragmentDurationPair nextFdp = fragmentDurationPairs[i + 1];
                if ((curFdp.firstFragment <= fragId) && (fragId < nextFdp.firstFragment))
                {
                    if (curFdp.duration <= 0)
                    {
                        fai = getNextValidFragment(i + 1);
                    }
                    else
                    {
                        fai                 = new FragmentAccessInformation();
                        fai.fragId          = fragId;
                        fai.fragDuration    = curFdp.duration;
                        fai.fragmentEndTime = (uint)curFdp.durationAccrued + curFdp.duration * (fragId - curFdp.firstFragment + 1);
                    }
                    break;
                }
                else if ((curFdp.firstFragment <= fragId) && endOfStreamEntry(nextFdp))
                {
                    if (curFdp.duration > 0)
                    {
                        timeResidue   = (uint)(totalDuration - curFdp.durationAccrued);
                        timeDistance  = (fragId - curFdp.firstFragment + 1) * curFdp.duration;
                        fragStartTime = (fragId - curFdp.firstFragment) * curFdp.duration;
                        if (timeResidue > fragStartTime)
                        {
                            if (!live || ((fragStartTime + curFdp.duration + curFdp.durationAccrued) <= totalDuration))
                            {
                                fai              = new FragmentAccessInformation();
                                fai.fragId       = fragId;
                                fai.fragDuration = curFdp.duration;
                                if (timeResidue >= timeDistance)
                                {
                                    fai.fragmentEndTime = (uint)curFdp.durationAccrued + timeDistance;
                                }
                                else
                                {
                                    fai.fragmentEndTime = (uint)curFdp.durationAccrued + timeResidue;
                                }
                                break;
                            }
                        }
                    }
                }
            }
            if (fai == null)
            {
                FragmentDurationPair lastFdp = fragmentDurationPairs[size];
                if (lastFdp.duration > 0 && fragId >= lastFdp.firstFragment)
                {
                    timeResidue   = (uint)(totalDuration - lastFdp.durationAccrued);
                    timeDistance  = (fragId - lastFdp.firstFragment + 1) * lastFdp.duration;
                    fragStartTime = (fragId - lastFdp.firstFragment) * lastFdp.duration;
                    if (timeResidue > fragStartTime)
                    {
                        if (!live || ((fragStartTime + lastFdp.duration + lastFdp.durationAccrued) <= totalDuration))
                        {
                            fai              = new FragmentAccessInformation();
                            fai.fragId       = fragId;
                            fai.fragDuration = lastFdp.duration;
                            if (timeResidue >= timeDistance)
                            {
                                fai.fragmentEndTime = (uint)lastFdp.durationAccrued + timeDistance;
                            }
                            else
                            {
                                fai.fragmentEndTime = (uint)lastFdp.durationAccrued + timeResidue;
                            }
                        }
                    }
                }
            }
            return(fai);
        }