diff --git a/library/src/main/java/com/google/android/exoplayer/FrameworkSampleSource.java b/library/src/main/java/com/google/android/exoplayer/FrameworkSampleSource.java index b9a8668f22..eef8c152e4 100644 --- a/library/src/main/java/com/google/android/exoplayer/FrameworkSampleSource.java +++ b/library/src/main/java/com/google/android/exoplayer/FrameworkSampleSource.java @@ -119,6 +119,8 @@ public final class FrameworkSampleSource implements SampleSource { headers = null; } + // SampleSource implementation. + @Override public boolean prepare(long positionUs) throws IOException { if (prepared) { @@ -156,11 +158,6 @@ public final class FrameworkSampleSource implements SampleSource { return tracks; } - @Override - public void continueBuffering(long positionUs) { - // MediaExtractor takes care of buffering. Do nothing. - } - @Override public TrackStream[] selectTracks(List oldStreams, List newSelections, long positionUs) { @@ -194,6 +191,44 @@ public final class FrameworkSampleSource implements SampleSource { return newStreams; } + @Override + public void continueBuffering(long positionUs) { + // MediaExtractor takes care of buffering. Do nothing. + } + + @Override + public void seekToUs(long positionUs) { + if (enabledTrackCount == 0) { + return; + } + seekToUsInternal(positionUs, false); + } + + @Override + public long getBufferedPositionUs() { + if (enabledTrackCount == 0) { + return C.END_OF_SOURCE_US; + } + + long bufferedDurationUs = extractor.getCachedDuration(); + if (bufferedDurationUs == -1) { + return C.UNKNOWN_TIME_US; + } + + long sampleTime = extractor.getSampleTime(); + return sampleTime == -1 ? C.END_OF_SOURCE_US : sampleTime + bufferedDurationUs; + } + + @Override + public void release() { + if (extractor != null) { + extractor.release(); + extractor = null; + } + } + + // TrackStream methods. + /* package */ long readReset(int track) { if (pendingResets[track]) { pendingResets[track] = false; @@ -242,36 +277,7 @@ public final class FrameworkSampleSource implements SampleSource { } } - @Override - public void seekToUs(long positionUs) { - if (enabledTrackCount == 0) { - return; - } - seekToUsInternal(positionUs, false); - } - - @Override - public long getBufferedPositionUs() { - if (enabledTrackCount == 0) { - return C.END_OF_SOURCE_US; - } - - long bufferedDurationUs = extractor.getCachedDuration(); - if (bufferedDurationUs == -1) { - return C.UNKNOWN_TIME_US; - } - - long sampleTime = extractor.getSampleTime(); - return sampleTime == -1 ? C.END_OF_SOURCE_US : sampleTime + bufferedDurationUs; - } - - @Override - public void release() { - if (extractor != null) { - extractor.release(); - extractor = null; - } - } + // Internal methods. @TargetApi(18) private DrmInitData getDrmInitDataV18() { diff --git a/library/src/main/java/com/google/android/exoplayer/MultiSampleSource.java b/library/src/main/java/com/google/android/exoplayer/MultiSampleSource.java index f25071bdcb..f9fdff3a87 100644 --- a/library/src/main/java/com/google/android/exoplayer/MultiSampleSource.java +++ b/library/src/main/java/com/google/android/exoplayer/MultiSampleSource.java @@ -80,6 +80,11 @@ public final class MultiSampleSource implements SampleSource { return true; } + @Override + public long getDurationUs() { + return durationUs; + } + @Override public TrackGroupArray getTrackGroups() { return trackGroups; @@ -118,18 +123,6 @@ public final class MultiSampleSource implements SampleSource { } } - @Override - public void seekToUs(long positionUs) { - for (SampleSource source : enabledSources) { - source.seekToUs(positionUs); - } - } - - @Override - public long getDurationUs() { - return durationUs; - } - @Override public long getBufferedPositionUs() { long bufferedPositionUs = durationUs != C.UNKNOWN_TIME_US ? durationUs : Long.MAX_VALUE; @@ -146,6 +139,13 @@ public final class MultiSampleSource implements SampleSource { return bufferedPositionUs == Long.MAX_VALUE ? C.UNKNOWN_TIME_US : bufferedPositionUs; } + @Override + public void seekToUs(long positionUs) { + for (SampleSource source : enabledSources) { + source.seekToUs(positionUs); + } + } + @Override public void release() { for (SampleSource source : sources) { @@ -153,6 +153,8 @@ public final class MultiSampleSource implements SampleSource { } } + // Internal methods. + private int selectTracks(SampleSource source, List allOldStreams, List allNewSelections, long positionUs, TrackStream[] allNewStreams) { // Get the subset of the old streams for the source. diff --git a/library/src/main/java/com/google/android/exoplayer/SingleSampleSource.java b/library/src/main/java/com/google/android/exoplayer/SingleSampleSource.java index a8bb0ac9bf..9189ce820c 100644 --- a/library/src/main/java/com/google/android/exoplayer/SingleSampleSource.java +++ b/library/src/main/java/com/google/android/exoplayer/SingleSampleSource.java @@ -104,10 +104,7 @@ public final class SingleSampleSource implements SampleSource, TrackStream, Load sampleData = new byte[INITIAL_SAMPLE_SIZE]; } - @Override - public void maybeThrowError() throws IOException { - loader.maybeThrowError(); - } + // SampleSource implementation. @Override public boolean prepare(long positionUs) { @@ -152,11 +149,37 @@ public final class SingleSampleSource implements SampleSource, TrackStream, Load // Do nothing. } + @Override + public long getBufferedPositionUs() { + return loadingFinished ? C.END_OF_SOURCE_US : 0; + } + + @Override + public void seekToUs(long positionUs) { + if (streamState == STREAM_STATE_END_OF_STREAM) { + pendingResetPositionUs = positionUs; + streamState = STREAM_STATE_SEND_SAMPLE; + } + } + + @Override + public void release() { + streamState = STREAM_STATE_END_OF_STREAM; + loader.release(); + } + + // TrackStream implementation. + @Override public boolean isReady() { return loadingFinished; } + @Override + public void maybeThrowError() throws IOException { + loader.maybeThrowError(); + } + @Override public long readReset() { long resetPositionUs = pendingResetPositionUs; @@ -189,25 +212,6 @@ public final class SingleSampleSource implements SampleSource, TrackStream, Load } } - @Override - public void seekToUs(long positionUs) { - if (streamState == STREAM_STATE_END_OF_STREAM) { - pendingResetPositionUs = positionUs; - streamState = STREAM_STATE_SEND_SAMPLE; - } - } - - @Override - public long getBufferedPositionUs() { - return loadingFinished ? C.END_OF_SOURCE_US : 0; - } - - @Override - public void release() { - streamState = STREAM_STATE_END_OF_STREAM; - loader.release(); - } - // Loader.Callback implementation. @Override @@ -259,7 +263,7 @@ public final class SingleSampleSource implements SampleSource, TrackStream, Load } } - // Private methods. + // Internal methods. private void maybeStartLoading() { if (loadingFinished || streamState == STREAM_STATE_END_OF_STREAM || loader.isLoading()) { diff --git a/library/src/main/java/com/google/android/exoplayer/chunk/ChunkSampleSource.java b/library/src/main/java/com/google/android/exoplayer/chunk/ChunkSampleSource.java index 7489a78639..184c8cab62 100644 --- a/library/src/main/java/com/google/android/exoplayer/chunk/ChunkSampleSource.java +++ b/library/src/main/java/com/google/android/exoplayer/chunk/ChunkSampleSource.java @@ -29,6 +29,7 @@ import com.google.android.exoplayer.extractor.DefaultTrackOutput; import com.google.android.exoplayer.upstream.Loader; import com.google.android.exoplayer.upstream.Loader.Loadable; import com.google.android.exoplayer.util.Assertions; +import com.google.android.exoplayer.util.Util; import android.os.Handler; import android.os.SystemClock; @@ -139,6 +140,8 @@ public class ChunkSampleSource implements SampleSource, TrackStream, Loader.Call pendingResetPositionUs = NO_RESET_PENDING; } + // SampleSource implementation. + @Override public boolean prepare(long positionUs) throws IOException { if (prepared) { @@ -224,11 +227,62 @@ public class ChunkSampleSource implements SampleSource, TrackStream, Loader.Call maybeStartLoading(); } + @Override + public long getBufferedPositionUs() { + if (loadingFinished) { + return C.END_OF_SOURCE_US; + } else if (isPendingReset()) { + return pendingResetPositionUs; + } else { + long largestParsedTimestampUs = sampleQueue.getLargestParsedTimestampUs(); + return largestParsedTimestampUs == Long.MIN_VALUE ? downstreamPositionUs + : largestParsedTimestampUs; + } + } + + @Override + public void seekToUs(long positionUs) { + downstreamPositionUs = positionUs; + lastSeekPositionUs = positionUs; + // If we're not pending a reset, see if we can seek within the sample queue. + boolean seekInsideBuffer = !isPendingReset() && sampleQueue.skipToKeyframeBefore(positionUs); + if (seekInsideBuffer) { + // We succeeded. All we need to do is discard any chunks that we've moved past. + boolean haveSamples = !sampleQueue.isEmpty(); + while (haveSamples && mediaChunks.size() > 1 + && mediaChunks.get(1).getFirstSampleIndex() <= sampleQueue.getReadIndex()) { + mediaChunks.removeFirst(); + } + } else { + // We failed, and need to restart. + restartFrom(positionUs); + } + // Either way, we need to send a discontinuity to the downstream components. + pendingReset = true; + } + + @Override + public void release() { + prepared = false; + trackEnabled = false; + loader.release(); + } + + // TrackStream implementation. + @Override public boolean isReady() { return loadingFinished || !sampleQueue.isEmpty(); } + @Override + public void maybeThrowError() throws IOException { + loader.maybeThrowError(); + if (currentLoadableHolder.chunk == null) { + chunkSource.maybeThrowError(); + } + } + @Override public long readReset() { if (pendingReset) { @@ -287,55 +341,6 @@ public class ChunkSampleSource implements SampleSource, TrackStream, Loader.Call return NOTHING_READ; } - @Override - public void seekToUs(long positionUs) { - downstreamPositionUs = positionUs; - lastSeekPositionUs = positionUs; - // If we're not pending a reset, see if we can seek within the sample queue. - boolean seekInsideBuffer = !isPendingReset() && sampleQueue.skipToKeyframeBefore(positionUs); - if (seekInsideBuffer) { - // We succeeded. All we need to do is discard any chunks that we've moved past. - boolean haveSamples = !sampleQueue.isEmpty(); - while (haveSamples && mediaChunks.size() > 1 - && mediaChunks.get(1).getFirstSampleIndex() <= sampleQueue.getReadIndex()) { - mediaChunks.removeFirst(); - } - } else { - // We failed, and need to restart. - restartFrom(positionUs); - } - // Either way, we need to send a discontinuity to the downstream components. - pendingReset = true; - } - - @Override - public void maybeThrowError() throws IOException { - loader.maybeThrowError(); - if (currentLoadableHolder.chunk == null) { - chunkSource.maybeThrowError(); - } - } - - @Override - public long getBufferedPositionUs() { - if (loadingFinished) { - return C.END_OF_SOURCE_US; - } else if (isPendingReset()) { - return pendingResetPositionUs; - } else { - long largestParsedTimestampUs = sampleQueue.getLargestParsedTimestampUs(); - return largestParsedTimestampUs == Long.MIN_VALUE ? downstreamPositionUs - : largestParsedTimestampUs; - } - } - - @Override - public void release() { - prepared = false; - trackEnabled = false; - loader.release(); - } - // Loadable.Callback implementation. @Override @@ -396,6 +401,8 @@ public class ChunkSampleSource implements SampleSource, TrackStream, Loader.Call } } + // Internal methods. + /** * Called when a sample has been read. Can be used to perform any modifications necessary before * the sample is returned. @@ -530,10 +537,6 @@ public class ChunkSampleSource implements SampleSource, TrackStream, Loader.Call return pendingResetPositionUs != NO_RESET_PENDING; } - protected final long usToMs(long timeUs) { - return timeUs / 1000; - } - private void notifyLoadStarted(final long length, final int type, final int trigger, final Format format, final long mediaStartTimeUs, final long mediaEndTimeUs) { if (eventHandler != null && eventListener != null) { @@ -541,7 +544,7 @@ public class ChunkSampleSource implements SampleSource, TrackStream, Loader.Call @Override public void run() { eventListener.onLoadStarted(eventSourceId, length, type, trigger, format, - usToMs(mediaStartTimeUs), usToMs(mediaEndTimeUs)); + Util.usToMs(mediaStartTimeUs), Util.usToMs(mediaEndTimeUs)); } }); } @@ -555,7 +558,8 @@ public class ChunkSampleSource implements SampleSource, TrackStream, Loader.Call @Override public void run() { eventListener.onLoadCompleted(eventSourceId, bytesLoaded, type, trigger, format, - usToMs(mediaStartTimeUs), usToMs(mediaEndTimeUs), elapsedRealtimeMs, loadDurationMs); + Util.usToMs(mediaStartTimeUs), Util.usToMs(mediaEndTimeUs), elapsedRealtimeMs, + loadDurationMs); } }); } @@ -588,8 +592,8 @@ public class ChunkSampleSource implements SampleSource, TrackStream, Loader.Call eventHandler.post(new Runnable() { @Override public void run() { - eventListener.onUpstreamDiscarded(eventSourceId, usToMs(mediaStartTimeUs), - usToMs(mediaEndTimeUs)); + eventListener.onUpstreamDiscarded(eventSourceId, Util.usToMs(mediaStartTimeUs), + Util.usToMs(mediaEndTimeUs)); } }); } @@ -602,7 +606,7 @@ public class ChunkSampleSource implements SampleSource, TrackStream, Loader.Call @Override public void run() { eventListener.onDownstreamFormatChanged(eventSourceId, format, trigger, - usToMs(positionUs)); + Util.usToMs(positionUs)); } }); } diff --git a/library/src/main/java/com/google/android/exoplayer/extractor/ExtractorSampleSource.java b/library/src/main/java/com/google/android/exoplayer/extractor/ExtractorSampleSource.java index 8e9a6faef2..9621f18cdc 100644 --- a/library/src/main/java/com/google/android/exoplayer/extractor/ExtractorSampleSource.java +++ b/library/src/main/java/com/google/android/exoplayer/extractor/ExtractorSampleSource.java @@ -329,6 +329,8 @@ public final class ExtractorSampleSource implements SampleSource, ExtractorOutpu pendingResetPositionUs = NO_RESET_PENDING; } + // SampleSource implementation. + @Override public boolean prepare(long positionUs) throws IOException { if (prepared) { @@ -418,12 +420,49 @@ public final class ExtractorSampleSource implements SampleSource, ExtractorOutpu maybeStartLoading(); } + @Override + public long getBufferedPositionUs() { + if (loadingFinished) { + return C.END_OF_SOURCE_US; + } else if (isPendingReset()) { + return pendingResetPositionUs; + } else { + long largestParsedTimestampUs = Long.MIN_VALUE; + for (int i = 0; i < sampleQueues.size(); i++) { + largestParsedTimestampUs = Math.max(largestParsedTimestampUs, + sampleQueues.valueAt(i).getLargestParsedTimestampUs()); + } + return largestParsedTimestampUs == Long.MIN_VALUE ? downstreamPositionUs + : largestParsedTimestampUs; + } + } + + @Override + public void seekToUs(long positionUs) { + seekToInternal(positionUs); + } + + @Override + public void release() { + enabledTrackCount = 0; + loader.release(); + } + + // TrackStream methods. + /* package */ boolean isReady(int track) { Assertions.checkState(trackEnabledStates[track]); return !sampleQueues.valueAt(track).isEmpty(); } + /* package */ void maybeThrowError() throws IOException { + if (fatalException != null) { + throw fatalException; + } + loader.maybeThrowError(); + } + /* package */ long readReset(int track) { if (pendingResets[track]) { pendingResets[track] = false; @@ -466,58 +505,6 @@ public final class ExtractorSampleSource implements SampleSource, ExtractorOutpu return TrackStream.NOTHING_READ; } - /* package */ void maybeThrowError() throws IOException { - if (fatalException != null) { - throw fatalException; - } - loader.maybeThrowError(); - } - - @Override - public void seekToUs(long positionUs) { - seekToInternal(positionUs); - } - - private void seekToInternal(long positionUs) { - // Treat all seeks into non-seekable media as being to t=0. - positionUs = !seekMap.isSeekable() ? 0 : positionUs; - lastSeekPositionUs = positionUs; - downstreamPositionUs = positionUs; - Arrays.fill(pendingResets, true); - // If we're not pending a reset, see if we can seek within the sample queues. - boolean seekInsideBuffer = !isPendingReset(); - for (int i = 0; seekInsideBuffer && i < sampleQueues.size(); i++) { - seekInsideBuffer &= sampleQueues.valueAt(i).skipToKeyframeBefore(positionUs); - } - // If we failed to seek within the sample queues, we need to restart. - if (!seekInsideBuffer) { - restartFrom(positionUs); - } - } - - @Override - public long getBufferedPositionUs() { - if (loadingFinished) { - return C.END_OF_SOURCE_US; - } else if (isPendingReset()) { - return pendingResetPositionUs; - } else { - long largestParsedTimestampUs = Long.MIN_VALUE; - for (int i = 0; i < sampleQueues.size(); i++) { - largestParsedTimestampUs = Math.max(largestParsedTimestampUs, - sampleQueues.valueAt(i).getLargestParsedTimestampUs()); - } - return largestParsedTimestampUs == Long.MIN_VALUE ? downstreamPositionUs - : largestParsedTimestampUs; - } - } - - @Override - public void release() { - enabledTrackCount = 0; - loader.release(); - } - // Loader.Callback implementation. @Override @@ -575,7 +562,24 @@ public final class ExtractorSampleSource implements SampleSource, ExtractorOutpu this.drmInitData = drmInitData; } - // Internal stuff. + // Internal methods. + + private void seekToInternal(long positionUs) { + // Treat all seeks into non-seekable media as being to t=0. + positionUs = !seekMap.isSeekable() ? 0 : positionUs; + lastSeekPositionUs = positionUs; + downstreamPositionUs = positionUs; + Arrays.fill(pendingResets, true); + // If we're not pending a reset, see if we can seek within the sample queues. + boolean seekInsideBuffer = !isPendingReset(); + for (int i = 0; seekInsideBuffer && i < sampleQueues.size(); i++) { + seekInsideBuffer &= sampleQueues.valueAt(i).skipToKeyframeBefore(positionUs); + } + // If we failed to seek within the sample queues, we need to restart. + if (!seekInsideBuffer) { + restartFrom(positionUs); + } + } private void restartFrom(long positionUs) { pendingResetPositionUs = positionUs; diff --git a/library/src/main/java/com/google/android/exoplayer/hls/HlsSampleSource.java b/library/src/main/java/com/google/android/exoplayer/hls/HlsSampleSource.java index c3585b8816..fc91a8aa18 100644 --- a/library/src/main/java/com/google/android/exoplayer/hls/HlsSampleSource.java +++ b/library/src/main/java/com/google/android/exoplayer/hls/HlsSampleSource.java @@ -32,6 +32,7 @@ import com.google.android.exoplayer.upstream.Loader; import com.google.android.exoplayer.upstream.Loader.Loadable; import com.google.android.exoplayer.util.Assertions; import com.google.android.exoplayer.util.MimeTypes; +import com.google.android.exoplayer.util.Util; import android.os.Handler; import android.os.SystemClock; @@ -128,6 +129,8 @@ public final class HlsSampleSource implements SampleSource, Loader.Callback { chunkOperationHolder = new ChunkOperationHolder(); } + // SampleSource implementation. + @Override public boolean prepare(long positionUs) throws IOException { if (prepared) { @@ -245,6 +248,47 @@ public final class HlsSampleSource implements SampleSource, Loader.Callback { maybeStartLoading(); } + @Override + public long getBufferedPositionUs() { + if (isPendingReset()) { + return pendingResetPositionUs; + } else if (loadingFinished) { + return C.END_OF_SOURCE_US; + } else { + long bufferedPositionUs = extractors.getLast().getLargestParsedTimestampUs(); + if (extractors.size() > 1) { + // When adapting from one format to the next, the penultimate extractor may have the largest + // parsed timestamp (e.g. if the last extractor hasn't parsed any timestamps yet). + bufferedPositionUs = Math.max(bufferedPositionUs, + extractors.get(extractors.size() - 2).getLargestParsedTimestampUs()); + } + if (previousTsLoadable != null) { + // Buffered position should be at least as large as the end time of the previously loaded + // chunk. + bufferedPositionUs = Math.max(previousTsLoadable.endTimeUs, bufferedPositionUs); + } + return bufferedPositionUs == Long.MIN_VALUE ? downstreamPositionUs + : bufferedPositionUs; + } + } + + @Override + public void seekToUs(long positionUs) { + seekToInternal(positionUs); + } + + @Override + public void release() { + enabledTrackCount = 0; + if (loadControlRegistered) { + loadControl.unregister(this); + loadControlRegistered = false; + } + loader.release(); + } + + // TrackStream methods. + /* package */ boolean isReady(int group) { Assertions.checkState(groupEnabledStates[group]); if (loadingFinished) { @@ -265,6 +309,13 @@ public final class HlsSampleSource implements SampleSource, Loader.Callback { return false; } + /* package */ void maybeThrowError() throws IOException { + loader.maybeThrowError(); + if (currentLoadable == null) { + chunkSource.maybeThrowError(); + } + } + /* package */ long readReset(int group) { if (pendingResets[group]) { pendingResets[group] = false; @@ -327,52 +378,6 @@ public final class HlsSampleSource implements SampleSource, Loader.Callback { return TrackStream.NOTHING_READ; } - /* package */ void maybeThrowError() throws IOException { - loader.maybeThrowError(); - if (currentLoadable == null) { - chunkSource.maybeThrowError(); - } - } - - @Override - public void seekToUs(long positionUs) { - seekToInternal(positionUs); - } - - @Override - public long getBufferedPositionUs() { - if (isPendingReset()) { - return pendingResetPositionUs; - } else if (loadingFinished) { - return C.END_OF_SOURCE_US; - } else { - long bufferedPositionUs = extractors.getLast().getLargestParsedTimestampUs(); - if (extractors.size() > 1) { - // When adapting from one format to the next, the penultimate extractor may have the largest - // parsed timestamp (e.g. if the last extractor hasn't parsed any timestamps yet). - bufferedPositionUs = Math.max(bufferedPositionUs, - extractors.get(extractors.size() - 2).getLargestParsedTimestampUs()); - } - if (previousTsLoadable != null) { - // Buffered position should be at least as large as the end time of the previously loaded - // chunk. - bufferedPositionUs = Math.max(previousTsLoadable.endTimeUs, bufferedPositionUs); - } - return bufferedPositionUs == Long.MIN_VALUE ? downstreamPositionUs - : bufferedPositionUs; - } - } - - @Override - public void release() { - enabledTrackCount = 0; - if (loadControlRegistered) { - loadControl.unregister(this); - loadControlRegistered = false; - } - loader.release(); - } - // Loader.Callback implementation. @Override @@ -425,7 +430,7 @@ public final class HlsSampleSource implements SampleSource, Loader.Callback { } } - // Internal stuff. + // Internal methods. /** * Builds tracks that are exposed by this {@link HlsSampleSource} instance, as well as internal @@ -707,10 +712,6 @@ public final class HlsSampleSource implements SampleSource, Loader.Callback { return pendingResetPositionUs != NO_RESET_PENDING; } - /* package */ long usToMs(long timeUs) { - return timeUs / 1000; - } - private void notifyLoadStarted(final long length, final int type, final int trigger, final Format format, final long mediaStartTimeUs, final long mediaEndTimeUs) { if (eventHandler != null && eventListener != null) { @@ -718,7 +719,7 @@ public final class HlsSampleSource implements SampleSource, Loader.Callback { @Override public void run() { eventListener.onLoadStarted(eventSourceId, length, type, trigger, format, - usToMs(mediaStartTimeUs), usToMs(mediaEndTimeUs)); + Util.usToMs(mediaStartTimeUs), Util.usToMs(mediaEndTimeUs)); } }); } @@ -732,7 +733,8 @@ public final class HlsSampleSource implements SampleSource, Loader.Callback { @Override public void run() { eventListener.onLoadCompleted(eventSourceId, bytesLoaded, type, trigger, format, - usToMs(mediaStartTimeUs), usToMs(mediaEndTimeUs), elapsedRealtimeMs, loadDurationMs); + Util.usToMs(mediaStartTimeUs), Util.usToMs(mediaEndTimeUs), elapsedRealtimeMs, + loadDurationMs); } }); } @@ -767,7 +769,7 @@ public final class HlsSampleSource implements SampleSource, Loader.Callback { @Override public void run() { eventListener.onDownstreamFormatChanged(eventSourceId, format, trigger, - usToMs(positionUs)); + Util.usToMs(positionUs)); } }); } 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 d4fcf8cfa6..46e32c520f 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 @@ -131,6 +131,16 @@ public final class Util { return context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_LEANBACK); } + /** + * Converts microseconds to milliseconds (rounding down). + * + * @param timeUs A microsecond value. + * @return The value in milliseconds. + */ + public static long usToMs(long timeUs) { + return timeUs / 1000; + } + /** * Converts the entirety of an {@link InputStream} to a byte array. *