From 1b957268a6b52e952ece23eb3515678344c8c4d9 Mon Sep 17 00:00:00 2001 From: Oliver Woodman Date: Wed, 9 Jul 2014 15:34:42 +0100 Subject: [PATCH] Add utility methods for inexact ceil/floor binary searches. This change also fixes issue #5 --- .../exoplayer/dash/DashMp4ChunkSource.java | 7 +- .../exoplayer/dash/DashWebmChunkSource.java | 4 +- .../SmoothStreamingManifest.java | 6 +- .../exoplayer/text/ttml/TtmlSubtitle.java | 6 +- .../google/android/exoplayer/util/Util.java | 87 +++++++++++++++++++ 5 files changed, 97 insertions(+), 13 deletions(-) diff --git a/library/src/main/java/com/google/android/exoplayer/dash/DashMp4ChunkSource.java b/library/src/main/java/com/google/android/exoplayer/dash/DashMp4ChunkSource.java index 9eafebbb8f3..e975660497c 100644 --- a/library/src/main/java/com/google/android/exoplayer/dash/DashMp4ChunkSource.java +++ b/library/src/main/java/com/google/android/exoplayer/dash/DashMp4ChunkSource.java @@ -33,6 +33,7 @@ import com.google.android.exoplayer.upstream.DataSource; import com.google.android.exoplayer.upstream.DataSpec; import com.google.android.exoplayer.upstream.NonBlockingInputStream; +import com.google.android.exoplayer.util.Util; import android.util.Log; @@ -170,8 +171,8 @@ public final void getChunkOperation(List queue, long seekP int nextIndex; if (queue.isEmpty()) { - nextIndex = Arrays.binarySearch(extractor.getSegmentIndex().timesUs, seekPositionUs); - nextIndex = nextIndex < 0 ? -nextIndex - 2 : nextIndex; + nextIndex = Util.binarySearchFloor(extractor.getSegmentIndex().timesUs, seekPositionUs, + true, true); } else { nextIndex = queue.get(out.queueSize - 1).nextChunkIndex; } @@ -196,7 +197,7 @@ public IOException getError() { public void onChunkLoadError(Chunk chunk, Exception e) { // Do nothing. } - + private static Chunk newInitializationChunk(Representation representation, FragmentedMp4Extractor extractor, DataSource dataSource, int trigger) { DataSpec dataSpec = new DataSpec(representation.uri, 0, representation.indexEnd + 1, diff --git a/library/src/main/java/com/google/android/exoplayer/dash/DashWebmChunkSource.java b/library/src/main/java/com/google/android/exoplayer/dash/DashWebmChunkSource.java index 410f720f3cb..57714f380e9 100644 --- a/library/src/main/java/com/google/android/exoplayer/dash/DashWebmChunkSource.java +++ b/library/src/main/java/com/google/android/exoplayer/dash/DashWebmChunkSource.java @@ -33,6 +33,7 @@ import com.google.android.exoplayer.upstream.DataSource; import com.google.android.exoplayer.upstream.DataSpec; import com.google.android.exoplayer.upstream.NonBlockingInputStream; +import com.google.android.exoplayer.util.Util; import android.util.Log; @@ -151,8 +152,7 @@ public final void getChunkOperation(List queue, long seekP int nextIndex; if (queue.isEmpty()) { - nextIndex = Arrays.binarySearch(extractor.getCues().timesUs, seekPositionUs); - nextIndex = nextIndex < 0 ? -nextIndex - 2 : nextIndex; + nextIndex = Util.binarySearchFloor(extractor.getCues().timesUs, seekPositionUs, true, true); } else { nextIndex = queue.get(out.queueSize - 1).nextChunkIndex; } diff --git a/library/src/main/java/com/google/android/exoplayer/smoothstreaming/SmoothStreamingManifest.java b/library/src/main/java/com/google/android/exoplayer/smoothstreaming/SmoothStreamingManifest.java index 185171bfe69..d6a739ee1b6 100644 --- a/library/src/main/java/com/google/android/exoplayer/smoothstreaming/SmoothStreamingManifest.java +++ b/library/src/main/java/com/google/android/exoplayer/smoothstreaming/SmoothStreamingManifest.java @@ -16,8 +16,8 @@ package com.google.android.exoplayer.smoothstreaming; import com.google.android.exoplayer.util.MimeTypes; +import com.google.android.exoplayer.util.Util; -import java.util.Arrays; import java.util.UUID; /** @@ -195,9 +195,7 @@ public StreamElement(int type, String subType, long timeScale, String name, * @return The index of the corresponding chunk. */ public int getChunkIndex(long timeUs) { - long time = (timeUs * timeScale) / 1000000L; - int chunkIndex = Arrays.binarySearch(chunkStartTimes, time); - return chunkIndex < 0 ? -chunkIndex - 2 : chunkIndex; + return Util.binarySearchFloor(chunkStartTimes, (timeUs * timeScale) / 1000000L, true, true); } /** diff --git a/library/src/main/java/com/google/android/exoplayer/text/ttml/TtmlSubtitle.java b/library/src/main/java/com/google/android/exoplayer/text/ttml/TtmlSubtitle.java index 9e7299a8d26..a0c4da091eb 100644 --- a/library/src/main/java/com/google/android/exoplayer/text/ttml/TtmlSubtitle.java +++ b/library/src/main/java/com/google/android/exoplayer/text/ttml/TtmlSubtitle.java @@ -16,8 +16,7 @@ package com.google.android.exoplayer.text.ttml; import com.google.android.exoplayer.text.Subtitle; - -import java.util.Arrays; +import com.google.android.exoplayer.util.Util; /** * A representation of a TTML subtitle. @@ -41,8 +40,7 @@ public long getStartTime() { @Override public int getNextEventTimeIndex(long timeUs) { - int index = Arrays.binarySearch(eventTimesUs, timeUs - startTimeUs); - index = index >= 0 ? index + 1 : ~index; + int index = Util.binarySearchCeil(eventTimesUs, timeUs - startTimeUs, false, false); return index < eventTimesUs.length ? index : -1; } diff --git a/library/src/main/java/com/google/android/exoplayer/util/Util.java b/library/src/main/java/com/google/android/exoplayer/util/Util.java index 19b535148f6..ee328ba8b0d 100644 --- a/library/src/main/java/com/google/android/exoplayer/util/Util.java +++ b/library/src/main/java/com/google/android/exoplayer/util/Util.java @@ -19,6 +19,9 @@ import java.io.IOException; import java.net.URL; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; import java.util.Locale; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; @@ -112,4 +115,88 @@ public static String toLowerInvariant(String text) { return text == null ? null : text.toLowerCase(Locale.US); } + /** + * Returns the index of the largest value in an array that is less than (or optionally equal to) + * a specified key. + *

+ * The search is performed using a binary search algorithm, and so the array must be sorted. + * + * @param a The array to search. + * @param key The key being searched for. + * @param inclusive If the key is present in the array, whether to return the corresponding index. + * If false then the returned index corresponds to the largest value in the array that is + * strictly less than the key. + * @param stayInBounds If true, then 0 will be returned in the case that the key is smaller than + * the smallest value in the array. If false then -1 will be returned. + */ + public static int binarySearchFloor(long[] a, long key, boolean inclusive, boolean stayInBounds) { + int index = Arrays.binarySearch(a, key); + index = index < 0 ? -(index + 2) : (inclusive ? index : (index - 1)); + return stayInBounds ? Math.max(0, index) : index; + } + + /** + * Returns the index of the smallest value in an array that is greater than (or optionally equal + * to) a specified key. + *

+ * The search is performed using a binary search algorithm, and so the array must be sorted. + * + * @param a The array to search. + * @param key The key being searched for. + * @param inclusive If the key is present in the array, whether to return the corresponding index. + * If false then the returned index corresponds to the smallest value in the array that is + * strictly greater than the key. + * @param stayInBounds If true, then {@code (a.length - 1)} will be returned in the case that the + * key is greater than the largest value in the array. If false then {@code a.length} will be + * returned. + */ + public static int binarySearchCeil(long[] a, long key, boolean inclusive, boolean stayInBounds) { + int index = Arrays.binarySearch(a, key); + index = index < 0 ? ~index : (inclusive ? index : (index + 1)); + return stayInBounds ? Math.min(a.length - 1, index) : index; + } + + /** + * Returns the index of the largest value in an list that is less than (or optionally equal to) + * a specified key. + *

+ * The search is performed using a binary search algorithm, and so the list must be sorted. + * + * @param list The list to search. + * @param key The key being searched for. + * @param inclusive If the key is present in the list, whether to return the corresponding index. + * If false then the returned index corresponds to the largest value in the list that is + * strictly less than the key. + * @param stayInBounds If true, then 0 will be returned in the case that the key is smaller than + * the smallest value in the list. If false then -1 will be returned. + */ + public static int binarySearchFloor(List> list, T key, + boolean inclusive, boolean stayInBounds) { + int index = Collections.binarySearch(list, key); + index = index < 0 ? -(index + 2) : (inclusive ? index : (index - 1)); + return stayInBounds ? Math.max(0, index) : index; + } + + /** + * Returns the index of the smallest value in an list that is greater than (or optionally equal + * to) a specified key. + *

+ * The search is performed using a binary search algorithm, and so the list must be sorted. + * + * @param list The list to search. + * @param key The key being searched for. + * @param inclusive If the key is present in the list, whether to return the corresponding index. + * If false then the returned index corresponds to the smallest value in the list that is + * strictly greater than the key. + * @param stayInBounds If true, then {@code (list.size() - 1)} will be returned in the case that + * the key is greater than the largest value in the list. If false then {@code list.size()} + * will be returned. + */ + public static int binarySearchCeil(List> list, T key, + boolean inclusive, boolean stayInBounds) { + int index = Collections.binarySearch(list, key); + index = index < 0 ? ~index : (inclusive ? index : (index + 1)); + return stayInBounds ? Math.min(list.size() - 1, index) : index; + } + }