diff options
Diffstat (limited to 'dom/media/Intervals.h')
-rw-r--r-- | dom/media/Intervals.h | 762 |
1 files changed, 762 insertions, 0 deletions
diff --git a/dom/media/Intervals.h b/dom/media/Intervals.h new file mode 100644 index 0000000000..ecda90d350 --- /dev/null +++ b/dom/media/Intervals.h @@ -0,0 +1,762 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef DOM_MEDIA_INTERVALS_H_ +#define DOM_MEDIA_INTERVALS_H_ + +#include <algorithm> +#include <type_traits> + +#include "nsTArray.h" +#include "nsString.h" +#include "nsPrintfCString.h" + +// Specialization for nsTArray CopyChooser. +namespace mozilla::media { +template <class T> +class IntervalSet; +class TimeUnit; +} // namespace mozilla::media + +template <class E> +struct nsTArray_RelocationStrategy<mozilla::media::IntervalSet<E>> { + typedef nsTArray_RelocateUsingMoveConstructor<mozilla::media::IntervalSet<E>> + Type; +}; + +namespace mozilla::media { + +/* Interval defines an interval between two points. Unlike a traditional + interval [A,B] where A <= x <= B, the upper boundary B is exclusive: A <= x < + B (e.g [A,B[ or [A,B) depending on where you're living) It provides basic + interval arithmetic and fuzzy edges. The type T must provides a default + constructor and +, -, <, <= and == operators. + */ +template <typename T> +class Interval { + public: + typedef Interval<T> SelfType; + + Interval() : mStart(T()), mEnd(T()), mFuzz(T()) {} + + template <typename StartArg, typename EndArg> + Interval(StartArg&& aStart, EndArg&& aEnd) + : mStart(aStart), mEnd(aEnd), mFuzz() { + MOZ_DIAGNOSTIC_ASSERT(mStart <= mEnd, "Invalid Interval"); + } + + template <typename StartArg, typename EndArg, typename FuzzArg> + Interval(StartArg&& aStart, EndArg&& aEnd, FuzzArg&& aFuzz) + : mStart(aStart), mEnd(aEnd), mFuzz(aFuzz) { + MOZ_DIAGNOSTIC_ASSERT(mStart <= mEnd, "Invalid Interval"); + } + + Interval(const SelfType& aOther) + : mStart(aOther.mStart), mEnd(aOther.mEnd), mFuzz(aOther.mFuzz) {} + + Interval(SelfType&& aOther) + : mStart(std::move(aOther.mStart)), + mEnd(std::move(aOther.mEnd)), + mFuzz(std::move(aOther.mFuzz)) {} + + SelfType& operator=(const SelfType& aOther) { + mStart = aOther.mStart; + mEnd = aOther.mEnd; + mFuzz = aOther.mFuzz; + return *this; + } + + SelfType& operator=(SelfType&& aOther) { + MOZ_ASSERT(&aOther != this, "self-moves are prohibited"); + this->~Interval(); + new (this) Interval(std::move(aOther)); + return *this; + } + + // Basic interval arithmetic operator definition. + SelfType operator+(const SelfType& aOther) const { + return SelfType(mStart + aOther.mStart, mEnd + aOther.mEnd, + mFuzz + aOther.mFuzz); + } + + SelfType operator+(const T& aVal) const { + return SelfType(mStart + aVal, mEnd + aVal, mFuzz); + } + + SelfType operator-(const SelfType& aOther) const { + return SelfType(mStart - aOther.mEnd, mEnd - aOther.mStart, + mFuzz + aOther.mFuzz); + } + + SelfType operator-(const T& aVal) const { + return SelfType(mStart - aVal, mEnd - aVal, mFuzz); + } + + SelfType& operator+=(const SelfType& aOther) { + mStart += aOther.mStart; + mEnd += aOther.mEnd; + mFuzz += aOther.mFuzz; + return *this; + } + + SelfType& operator+=(const T& aVal) { + mStart += aVal; + mEnd += aVal; + return *this; + } + + SelfType& operator-=(const SelfType& aOther) { + mStart -= aOther.mStart; + mEnd -= aOther.mEnd; + mFuzz += aOther.mFuzz; + return *this; + } + + SelfType& operator-=(const T& aVal) { + mStart -= aVal; + mEnd -= aVal; + return *this; + } + + bool operator==(const SelfType& aOther) const { + return mStart == aOther.mStart && mEnd == aOther.mEnd; + } + + bool operator!=(const SelfType& aOther) const { return !(*this == aOther); } + + bool Contains(const T& aX) const { + return mStart - mFuzz <= aX && aX < mEnd + mFuzz; + } + + bool ContainsStrict(const T& aX) const { return mStart <= aX && aX < mEnd; } + + bool ContainsWithStrictEnd(const T& aX) const { + return mStart - mFuzz <= aX && aX < mEnd; + } + + bool Contains(const SelfType& aOther) const { + return (mStart - mFuzz <= aOther.mStart + aOther.mFuzz) && + (aOther.mEnd - aOther.mFuzz <= mEnd + mFuzz); + } + + bool ContainsStrict(const SelfType& aOther) const { + return mStart <= aOther.mStart && aOther.mEnd <= mEnd; + } + + bool ContainsWithStrictEnd(const SelfType& aOther) const { + return (mStart - mFuzz <= aOther.mStart + aOther.mFuzz) && + aOther.mEnd <= mEnd; + } + + bool Intersects(const SelfType& aOther) const { + return (mStart - mFuzz < aOther.mEnd + aOther.mFuzz) && + (aOther.mStart - aOther.mFuzz < mEnd + mFuzz); + } + + bool IntersectsStrict(const SelfType& aOther) const { + return mStart < aOther.mEnd && aOther.mStart < mEnd; + } + + // Same as Intersects, but including the boundaries. + bool Touches(const SelfType& aOther) const { + return (mStart - mFuzz <= aOther.mEnd + aOther.mFuzz) && + (aOther.mStart - aOther.mFuzz <= mEnd + mFuzz); + } + + // Returns true if aOther is strictly to the right of this and contiguous. + // This operation isn't commutative. + bool Contiguous(const SelfType& aOther) const { + return mEnd <= aOther.mStart && + aOther.mStart - mEnd <= mFuzz + aOther.mFuzz; + } + + bool RightOf(const SelfType& aOther) const { + return aOther.mEnd - aOther.mFuzz <= mStart + mFuzz; + } + + bool LeftOf(const SelfType& aOther) const { + return mEnd - mFuzz <= aOther.mStart + aOther.mFuzz; + } + + SelfType Span(const SelfType& aOther) const { + if (IsEmpty()) { + return aOther; + } + SelfType result(*this); + if (aOther.mStart < mStart) { + result.mStart = aOther.mStart; + } + if (mEnd < aOther.mEnd) { + result.mEnd = aOther.mEnd; + } + if (mFuzz < aOther.mFuzz) { + result.mFuzz = aOther.mFuzz; + } + return result; + } + + SelfType Intersection(const SelfType& aOther) const { + const T& s = std::max(mStart, aOther.mStart); + const T& e = std::min(mEnd, aOther.mEnd); + const T& f = std::max(mFuzz, aOther.mFuzz); + if (s < e) { + return SelfType(s, e, f); + } + // Return an empty interval. + return SelfType(); + } + + T Length() const { return mEnd - mStart; } + + bool IsEmpty() const { return mStart == mEnd; } + + void SetFuzz(const T& aFuzz) { mFuzz = aFuzz; } + + // Returns true if the two intervals intersect with this being on the right + // of aOther + bool TouchesOnRight(const SelfType& aOther) const { + return aOther.mStart <= mStart && + (mStart - mFuzz <= aOther.mEnd + aOther.mFuzz) && + (aOther.mStart - aOther.mFuzz <= mEnd + mFuzz); + } + + // Returns true if the two intervals intersect with this being on the right + // of aOther, ignoring fuzz. + bool TouchesOnRightStrict(const SelfType& aOther) const { + return aOther.mStart <= mStart && mStart <= aOther.mEnd; + } + + nsCString ToString() const { + if constexpr (std::is_same_v<T, TimeUnit>) { + return nsPrintfCString("[%s, %s](%s)", mStart.ToString().get(), + mEnd.ToString().get(), mFuzz.ToString().get()); + } else if constexpr (std::is_same_v<T, double>) { + return nsPrintfCString("[%lf, %lf](%lf)", mStart, mEnd, mFuzz); + } + } + + T mStart; + T mEnd; + T mFuzz; +}; + +// An IntervalSet in a collection of Intervals. The IntervalSet is always +// normalized. +template <typename T> +class IntervalSet { + public: + typedef IntervalSet<T> SelfType; + typedef Interval<T> ElemType; + typedef AutoTArray<ElemType, 4> ContainerType; + typedef typename ContainerType::index_type IndexType; + + IntervalSet() = default; + virtual ~IntervalSet() = default; + + IntervalSet(const SelfType& aOther) : mIntervals(aOther.mIntervals.Clone()) {} + + IntervalSet(SelfType&& aOther) { + mIntervals.AppendElements(std::move(aOther.mIntervals)); + } + + explicit IntervalSet(const ElemType& aOther) { + if (!aOther.IsEmpty()) { + mIntervals.AppendElement(aOther); + } + } + + explicit IntervalSet(ElemType&& aOther) { + if (!aOther.IsEmpty()) { + mIntervals.AppendElement(std::move(aOther)); + } + } + + bool operator==(const SelfType& aOther) const { + return mIntervals == aOther.mIntervals; + } + + bool operator!=(const SelfType& aOther) const { + return mIntervals != aOther.mIntervals; + } + + SelfType& operator=(const SelfType& aOther) { + mIntervals = aOther.mIntervals.Clone(); + return *this; + } + + SelfType& operator=(SelfType&& aOther) { + MOZ_ASSERT(&aOther != this, "self-moves are prohibited"); + this->~IntervalSet(); + new (this) IntervalSet(std::move(aOther)); + return *this; + } + + SelfType& operator=(const ElemType& aInterval) { + mIntervals.Clear(); + if (!aInterval.IsEmpty()) { + mIntervals.AppendElement(aInterval); + } + return *this; + } + + SelfType& operator=(ElemType&& aInterval) { + mIntervals.Clear(); + if (!aInterval.IsEmpty()) { + mIntervals.AppendElement(std::move(aInterval)); + } + return *this; + } + + SelfType& Add(const SelfType& aIntervals) { + if (aIntervals.mIntervals.Length() == 1) { + Add(aIntervals.mIntervals[0]); + } else { + mIntervals.AppendElements(aIntervals.mIntervals); + Normalize(); + } + return *this; + } + + SelfType& Add(const ElemType& aInterval) { + if (aInterval.IsEmpty()) { + return *this; + } + if (mIntervals.IsEmpty()) { + mIntervals.AppendElement(aInterval); + return *this; + } + ElemType& last = mIntervals.LastElement(); + if (aInterval.TouchesOnRight(last)) { + last = last.Span(aInterval); + return *this; + } + // Most of our actual usage is adding an interval that will be outside the + // range. We can speed up normalization here. + if (aInterval.RightOf(last)) { + mIntervals.AppendElement(aInterval); + return *this; + } + + ContainerType normalized; + ElemType current(aInterval); + IndexType i = 0; + for (; i < mIntervals.Length(); i++) { + ElemType& interval = mIntervals[i]; + if (current.Touches(interval)) { + current = current.Span(interval); + } else if (current.LeftOf(interval)) { + break; + } else { + normalized.AppendElement(std::move(interval)); + } + } + normalized.AppendElement(std::move(current)); + for (; i < mIntervals.Length(); i++) { + normalized.AppendElement(std::move(mIntervals[i])); + } + mIntervals.Clear(); + mIntervals.AppendElements(std::move(normalized)); + + return *this; + } + + SelfType& operator+=(const SelfType& aIntervals) { + Add(aIntervals); + return *this; + } + + SelfType& operator+=(const ElemType& aInterval) { + Add(aInterval); + return *this; + } + + SelfType operator+(const SelfType& aIntervals) const { + SelfType intervals(*this); + intervals.Add(aIntervals); + return intervals; + } + + SelfType operator+(const ElemType& aInterval) const { + SelfType intervals(*this); + intervals.Add(aInterval); + return intervals; + } + + friend SelfType operator+(const ElemType& aInterval, + const SelfType& aIntervals) { + SelfType intervals; + intervals.Add(aInterval); + intervals.Add(aIntervals); + return intervals; + } + + // Excludes an interval from an IntervalSet. + SelfType& operator-=(const ElemType& aInterval) { + if (aInterval.IsEmpty() || mIntervals.IsEmpty()) { + return *this; + } + if (mIntervals.Length() == 1 && + mIntervals[0].TouchesOnRightStrict(aInterval)) { + // Fast path when we're removing from the front of a set with a + // single interval. This is common for the buffered time ranges + // we see on Twitch. + if (aInterval.mEnd >= mIntervals[0].mEnd) { + mIntervals.RemoveElementAt(0); + } else { + mIntervals[0].mStart = aInterval.mEnd; + mIntervals[0].mFuzz = std::max(mIntervals[0].mFuzz, aInterval.mFuzz); + } + return *this; + } + + // General case performed by inverting aInterval within the bounds of + // mIntervals and then doing the intersection. + T firstEnd = std::max(mIntervals[0].mStart, aInterval.mStart); + T secondStart = std::min(mIntervals.LastElement().mEnd, aInterval.mEnd); + ElemType startInterval(mIntervals[0].mStart, firstEnd); + ElemType endInterval(secondStart, mIntervals.LastElement().mEnd); + SelfType intervals(std::move(startInterval)); + intervals += std::move(endInterval); + return Intersection(intervals); + } + + SelfType& operator-=(const SelfType& aIntervals) { + for (const auto& interval : aIntervals.mIntervals) { + *this -= interval; + } + return *this; + } + + SelfType operator-(const SelfType& aInterval) const { + SelfType intervals(*this); + intervals -= aInterval; + return intervals; + } + + SelfType operator-(const ElemType& aInterval) const { + SelfType intervals(*this); + intervals -= aInterval; + return intervals; + } + + // Mutate this IntervalSet to be the union of this and aOther. + SelfType& Union(const SelfType& aOther) { + Add(aOther); + return *this; + } + + SelfType& Union(const ElemType& aInterval) { + Add(aInterval); + return *this; + } + + // Mutate this TimeRange to be the intersection of this and aOther. + SelfType& Intersection(const SelfType& aOther) { + ContainerType intersection; + + // Ensure the intersection has enough capacity to store the upper bound on + // the intersection size. This ensures that we don't spend time reallocating + // the storage as we append, at the expense of extra memory. + intersection.SetCapacity(std::max(aOther.Length(), mIntervals.Length())); + + const ContainerType& other = aOther.mIntervals; + IndexType i = 0, j = 0; + for (; i < mIntervals.Length() && j < other.Length();) { + if (mIntervals[i].IntersectsStrict(other[j])) { + intersection.AppendElement(mIntervals[i].Intersection(other[j])); + } + if (mIntervals[i].mEnd < other[j].mEnd) { + i++; + } else { + j++; + } + } + mIntervals = std::move(intersection); + return *this; + } + + SelfType& Intersection(const ElemType& aInterval) { + SelfType intervals(aInterval); + return Intersection(intervals); + } + + const ElemType& operator[](IndexType aIndex) const { + return mIntervals[aIndex]; + } + + // Returns the start boundary of the first interval. Or a default constructed + // T if IntervalSet is empty (and aExists if provided will be set to false). + T GetStart(bool* aExists = nullptr) const { + bool exists = !mIntervals.IsEmpty(); + + if (aExists) { + *aExists = exists; + } + + if (exists) { + return mIntervals[0].mStart; + } else { + return T(); + } + } + + // Returns the end boundary of the last interval. Or a default constructed T + // if IntervalSet is empty (and aExists if provided will be set to false). + T GetEnd(bool* aExists = nullptr) const { + bool exists = !mIntervals.IsEmpty(); + if (aExists) { + *aExists = exists; + } + + if (exists) { + return mIntervals.LastElement().mEnd; + } else { + return T(); + } + } + + IndexType Length() const { return mIntervals.Length(); } + + bool IsEmpty() const { return mIntervals.IsEmpty(); } + + T Start(IndexType aIndex) const { return mIntervals[aIndex].mStart; } + + T Start(IndexType aIndex, bool& aExists) const { + aExists = aIndex < mIntervals.Length(); + + if (aExists) { + return mIntervals[aIndex].mStart; + } else { + return T(); + } + } + + T End(IndexType aIndex) const { return mIntervals[aIndex].mEnd; } + + T End(IndexType aIndex, bool& aExists) const { + aExists = aIndex < mIntervals.Length(); + + if (aExists) { + return mIntervals[aIndex].mEnd; + } else { + return T(); + } + } + + bool Contains(const ElemType& aInterval) const { + for (const auto& interval : mIntervals) { + if (interval.Contains(aInterval)) { + return true; + } + } + return false; + } + + bool ContainsStrict(const ElemType& aInterval) const { + for (const auto& interval : mIntervals) { + if (interval.ContainsStrict(aInterval)) { + return true; + } + } + return false; + } + + bool Contains(const T& aX) const { + for (const auto& interval : mIntervals) { + if (interval.Contains(aX)) { + return true; + } + } + return false; + } + + bool ContainsStrict(const T& aX) const { + for (const auto& interval : mIntervals) { + if (interval.ContainsStrict(aX)) { + return true; + } + } + return false; + } + + bool ContainsWithStrictEnd(const T& aX) const { + for (const auto& interval : mIntervals) { + if (interval.ContainsWithStrictEnd(aX)) { + return true; + } + } + return false; + } + + bool ContainsWithStrictEnd(const ElemType& aInterval) const { + for (const auto& interval : mIntervals) { + if (interval.ContainsWithStrictEnd(aInterval)) { + return true; + } + } + return false; + } + + bool Intersects(const ElemType& aInterval) const { + for (const auto& interval : mIntervals) { + if (interval.Intersects(aInterval)) { + return true; + } + } + return false; + } + + bool IntersectsStrict(const ElemType& aInterval) const { + for (const auto& interval : mIntervals) { + if (interval.IntersectsStrict(aInterval)) { + return true; + } + } + return false; + } + + // Returns if there's any intersection between this and aOther. + bool IntersectsStrict(const SelfType& aOther) const { + const ContainerType& other = aOther.mIntervals; + IndexType i = 0, j = 0; + for (; i < mIntervals.Length() && j < other.Length();) { + if (mIntervals[i].IntersectsStrict(other[j])) { + return true; + } + if (mIntervals[i].mEnd < other[j].mEnd) { + i++; + } else { + j++; + } + } + return false; + } + + bool IntersectsWithStrictEnd(const ElemType& aInterval) const { + for (const auto& interval : mIntervals) { + if (interval.IntersectsWithStrictEnd(aInterval)) { + return true; + } + } + return false; + } + + // Shift all values by aOffset. + SelfType& Shift(const T& aOffset) { + for (auto& interval : mIntervals) { + interval.mStart = interval.mStart + aOffset; + interval.mEnd = interval.mEnd + aOffset; + } + return *this; + } + + void SetFuzz(const T& aFuzz) { + for (auto& interval : mIntervals) { + interval.SetFuzz(aFuzz); + } + MergeOverlappingIntervals(); + } + + static const IndexType NoIndex = IndexType(-1); + + IndexType Find(const T& aValue) const { + for (IndexType i = 0; i < mIntervals.Length(); i++) { + if (mIntervals[i].Contains(aValue)) { + return i; + } + } + return NoIndex; + } + + // Methods for range-based for loops. + typename ContainerType::iterator begin() { return mIntervals.begin(); } + + typename ContainerType::const_iterator begin() const { + return mIntervals.begin(); + } + + typename ContainerType::iterator end() { return mIntervals.end(); } + + typename ContainerType::const_iterator end() const { + return mIntervals.end(); + } + + ElemType& LastInterval() { + MOZ_ASSERT(!mIntervals.IsEmpty()); + return mIntervals.LastElement(); + } + + const ElemType& LastInterval() const { + MOZ_ASSERT(!mIntervals.IsEmpty()); + return mIntervals.LastElement(); + } + + void Clear() { mIntervals.Clear(); } + + protected: + ContainerType mIntervals; + + private: + void Normalize() { + if (mIntervals.Length() < 2) { + return; + } + mIntervals.Sort(CompareIntervals()); + MergeOverlappingIntervals(); + } + + void MergeOverlappingIntervals() { + if (mIntervals.Length() < 2) { + return; + } + + // This merges the intervals in place. + IndexType read = 0; + IndexType write = 0; + while (read < mIntervals.Length()) { + ElemType current(mIntervals[read]); + read++; + while (read < mIntervals.Length() && current.Touches(mIntervals[read])) { + current = current.Span(mIntervals[read]); + read++; + } + mIntervals[write] = current; + write++; + } + mIntervals.SetLength(write); + } + + struct CompareIntervals { + bool Equals(const ElemType& aT1, const ElemType& aT2) const { + return aT1.mStart == aT2.mStart && aT1.mEnd == aT2.mEnd; + } + + bool LessThan(const ElemType& aT1, const ElemType& aT2) const { + return aT1.mStart - aT1.mFuzz < aT2.mStart + aT2.mFuzz; + } + }; +}; + +// clang doesn't allow for this to be defined inline of IntervalSet. +template <typename T> +IntervalSet<T> Union(const IntervalSet<T>& aIntervals1, + const IntervalSet<T>& aIntervals2) { + IntervalSet<T> intervals(aIntervals1); + intervals.Union(aIntervals2); + return intervals; +} + +template <typename T> +IntervalSet<T> Intersection(const IntervalSet<T>& aIntervals1, + const IntervalSet<T>& aIntervals2) { + IntervalSet<T> intersection(aIntervals1); + intersection.Intersection(aIntervals2); + return intersection; +} + +} // namespace mozilla::media + +#endif // DOM_MEDIA_INTERVALS_H_ |