summaryrefslogtreecommitdiffstats
path: root/mobile/android/exoplayer2/src/main/java/org/mozilla/thirdparty/com/google/android/exoplayer2/upstream/cache/CachedContent.java
diff options
context:
space:
mode:
Diffstat (limited to 'mobile/android/exoplayer2/src/main/java/org/mozilla/thirdparty/com/google/android/exoplayer2/upstream/cache/CachedContent.java')
-rw-r--r--mobile/android/exoplayer2/src/main/java/org/mozilla/thirdparty/com/google/android/exoplayer2/upstream/cache/CachedContent.java208
1 files changed, 208 insertions, 0 deletions
diff --git a/mobile/android/exoplayer2/src/main/java/org/mozilla/thirdparty/com/google/android/exoplayer2/upstream/cache/CachedContent.java b/mobile/android/exoplayer2/src/main/java/org/mozilla/thirdparty/com/google/android/exoplayer2/upstream/cache/CachedContent.java
new file mode 100644
index 0000000000..660a2a3cb3
--- /dev/null
+++ b/mobile/android/exoplayer2/src/main/java/org/mozilla/thirdparty/com/google/android/exoplayer2/upstream/cache/CachedContent.java
@@ -0,0 +1,208 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.mozilla.thirdparty.com.google.android.exoplayer2.upstream.cache;
+
+import androidx.annotation.Nullable;
+import org.mozilla.thirdparty.com.google.android.exoplayer2.util.Assertions;
+import org.mozilla.thirdparty.com.google.android.exoplayer2.util.Log;
+import java.io.File;
+import java.util.TreeSet;
+
+/** Defines the cached content for a single stream. */
+/* package */ final class CachedContent {
+
+ private static final String TAG = "CachedContent";
+
+ /** The cache file id that uniquely identifies the original stream. */
+ public final int id;
+ /** The cache key that uniquely identifies the original stream. */
+ public final String key;
+ /** The cached spans of this content. */
+ private final TreeSet<SimpleCacheSpan> cachedSpans;
+ /** Metadata values. */
+ private DefaultContentMetadata metadata;
+ /** Whether the content is locked. */
+ private boolean locked;
+
+ /**
+ * Creates a CachedContent.
+ *
+ * @param id The cache file id.
+ * @param key The cache stream key.
+ */
+ public CachedContent(int id, String key) {
+ this(id, key, DefaultContentMetadata.EMPTY);
+ }
+
+ public CachedContent(int id, String key, DefaultContentMetadata metadata) {
+ this.id = id;
+ this.key = key;
+ this.metadata = metadata;
+ this.cachedSpans = new TreeSet<>();
+ }
+
+ /** Returns the metadata. */
+ public DefaultContentMetadata getMetadata() {
+ return metadata;
+ }
+
+ /**
+ * Applies {@code mutations} to the metadata.
+ *
+ * @return Whether {@code mutations} changed any metadata.
+ */
+ public boolean applyMetadataMutations(ContentMetadataMutations mutations) {
+ DefaultContentMetadata oldMetadata = metadata;
+ metadata = metadata.copyWithMutationsApplied(mutations);
+ return !metadata.equals(oldMetadata);
+ }
+
+ /** Returns whether the content is locked. */
+ public boolean isLocked() {
+ return locked;
+ }
+
+ /** Sets the locked state of the content. */
+ public void setLocked(boolean locked) {
+ this.locked = locked;
+ }
+
+ /** Adds the given {@link SimpleCacheSpan} which contains a part of the content. */
+ public void addSpan(SimpleCacheSpan span) {
+ cachedSpans.add(span);
+ }
+
+ /** Returns a set of all {@link SimpleCacheSpan}s. */
+ public TreeSet<SimpleCacheSpan> getSpans() {
+ return cachedSpans;
+ }
+
+ /**
+ * Returns the span containing the position. If there isn't one, it returns a hole span
+ * which defines the maximum extents of the hole in the cache.
+ */
+ public SimpleCacheSpan getSpan(long position) {
+ SimpleCacheSpan lookupSpan = SimpleCacheSpan.createLookup(key, position);
+ SimpleCacheSpan floorSpan = cachedSpans.floor(lookupSpan);
+ if (floorSpan != null && floorSpan.position + floorSpan.length > position) {
+ return floorSpan;
+ }
+ SimpleCacheSpan ceilSpan = cachedSpans.ceiling(lookupSpan);
+ return ceilSpan == null ? SimpleCacheSpan.createOpenHole(key, position)
+ : SimpleCacheSpan.createClosedHole(key, position, ceilSpan.position - position);
+ }
+
+ /**
+ * Returns the length of the cached data block starting from the {@code position} to the block end
+ * up to {@code length} bytes. If the {@code position} isn't cached then -(the length of the gap
+ * to the next cached data up to {@code length} bytes) is returned.
+ *
+ * @param position The starting position of the data.
+ * @param length The maximum length of the data to be returned.
+ * @return the length of the cached or not cached data block length.
+ */
+ public long getCachedBytesLength(long position, long length) {
+ SimpleCacheSpan span = getSpan(position);
+ if (span.isHoleSpan()) {
+ // We don't have a span covering the start of the queried region.
+ return -Math.min(span.isOpenEnded() ? Long.MAX_VALUE : span.length, length);
+ }
+ long queryEndPosition = position + length;
+ long currentEndPosition = span.position + span.length;
+ if (currentEndPosition < queryEndPosition) {
+ for (SimpleCacheSpan next : cachedSpans.tailSet(span, false)) {
+ if (next.position > currentEndPosition) {
+ // There's a hole in the cache within the queried region.
+ break;
+ }
+ // We expect currentEndPosition to always equal (next.position + next.length), but
+ // perform a max check anyway to guard against the existence of overlapping spans.
+ currentEndPosition = Math.max(currentEndPosition, next.position + next.length);
+ if (currentEndPosition >= queryEndPosition) {
+ // We've found spans covering the queried region.
+ break;
+ }
+ }
+ }
+ return Math.min(currentEndPosition - position, length);
+ }
+
+ /**
+ * Sets the given span's last touch timestamp. The passed span becomes invalid after this call.
+ *
+ * @param cacheSpan Span to be copied and updated.
+ * @param lastTouchTimestamp The new last touch timestamp.
+ * @param updateFile Whether the span file should be renamed to have its timestamp match the new
+ * last touch time.
+ * @return A span with the updated last touch timestamp.
+ */
+ public SimpleCacheSpan setLastTouchTimestamp(
+ SimpleCacheSpan cacheSpan, long lastTouchTimestamp, boolean updateFile) {
+ Assertions.checkState(cachedSpans.remove(cacheSpan));
+ File file = cacheSpan.file;
+ if (updateFile) {
+ File directory = file.getParentFile();
+ long position = cacheSpan.position;
+ File newFile = SimpleCacheSpan.getCacheFile(directory, id, position, lastTouchTimestamp);
+ if (file.renameTo(newFile)) {
+ file = newFile;
+ } else {
+ Log.w(TAG, "Failed to rename " + file + " to " + newFile);
+ }
+ }
+ SimpleCacheSpan newCacheSpan =
+ cacheSpan.copyWithFileAndLastTouchTimestamp(file, lastTouchTimestamp);
+ cachedSpans.add(newCacheSpan);
+ return newCacheSpan;
+ }
+
+ /** Returns whether there are any spans cached. */
+ public boolean isEmpty() {
+ return cachedSpans.isEmpty();
+ }
+
+ /** Removes the given span from cache. */
+ public boolean removeSpan(CacheSpan span) {
+ if (cachedSpans.remove(span)) {
+ span.file.delete();
+ return true;
+ }
+ return false;
+ }
+
+ @Override
+ public int hashCode() {
+ int result = id;
+ result = 31 * result + key.hashCode();
+ result = 31 * result + metadata.hashCode();
+ return result;
+ }
+
+ @Override
+ public boolean equals(@Nullable Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ CachedContent that = (CachedContent) o;
+ return id == that.id
+ && key.equals(that.key)
+ && cachedSpans.equals(that.cachedSpans)
+ && metadata.equals(that.metadata);
+ }
+}