Skip to content
Toggle navigation
P
Projects
G
Groups
S
Snippets
Help
SDK
/
exoplayer
This project
Loading...
Sign in
Toggle navigation
Go to a project
Project
Repository
Issues
0
Merge Requests
0
Pipelines
Wiki
Snippets
Settings
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Commit
235df090
authored
May 29, 2020
by
olly
Committed by
Oliver Woodman
May 29, 2020
Browse files
Options
_('Browse Files')
Download
Email Patches
Plain Diff
Support multiple non-overlapping write locks in SimpleCache
Issue: #5978 PiperOrigin-RevId: 313802629
parent
52e39cd7
Expand all
Show whitespace changes
Inline
Side-by-side
Showing
12 changed files
with
176 additions
and
67 deletions
RELEASENOTES.md
library/core/src/main/java/com/google/android/exoplayer2/offline/SegmentDownloader.java
library/core/src/main/java/com/google/android/exoplayer2/upstream/cache/Cache.java
library/core/src/main/java/com/google/android/exoplayer2/upstream/cache/CacheDataSource.java
library/core/src/main/java/com/google/android/exoplayer2/upstream/cache/CacheSpan.java
library/core/src/main/java/com/google/android/exoplayer2/upstream/cache/CachedContent.java
library/core/src/main/java/com/google/android/exoplayer2/upstream/cache/CachedContentIndex.java
library/core/src/main/java/com/google/android/exoplayer2/upstream/cache/SimpleCache.java
library/core/src/main/java/com/google/android/exoplayer2/upstream/cache/SimpleCacheSpan.java
library/core/src/test/java/com/google/android/exoplayer2/upstream/cache/CacheDataSourceTest.java
library/core/src/test/java/com/google/android/exoplayer2/upstream/cache/CachedContentIndexTest.java
library/core/src/test/java/com/google/android/exoplayer2/upstream/cache/SimpleCacheTest.java
RELEASENOTES.md
View file @
235df090
...
@@ -134,6 +134,8 @@
...
@@ -134,6 +134,8 @@
*
Downloads and caching:
*
Downloads and caching:
*
Merge downloads in
`SegmentDownloader`
to improve overall download speed
*
Merge downloads in
`SegmentDownloader`
to improve overall download speed
(
[
#5978
](
https://github.com/google/ExoPlayer/issues/5978
)
).
(
[
#5978
](
https://github.com/google/ExoPlayer/issues/5978
)
).
*
Support multiple non-overlapping write locks for the same key in
`SimpleCache`
.
*
Replace
`CacheDataSinkFactory`
and
`CacheDataSourceFactory`
with
*
Replace
`CacheDataSinkFactory`
and
`CacheDataSourceFactory`
with
`CacheDataSink.Factory`
and
`CacheDataSource.Factory`
respectively.
`CacheDataSink.Factory`
and
`CacheDataSource.Factory`
respectively.
*
Remove
`DownloadConstructorHelper`
and use
`CacheDataSource.Factory`
*
Remove
`DownloadConstructorHelper`
and use
`CacheDataSource.Factory`
...
...
library/core/src/main/java/com/google/android/exoplayer2/offline/SegmentDownloader.java
View file @
235df090
...
@@ -31,8 +31,10 @@ import com.google.android.exoplayer2.upstream.cache.CacheKeyFactory;
...
@@ -31,8 +31,10 @@ import com.google.android.exoplayer2.upstream.cache.CacheKeyFactory;
import
com.google.android.exoplayer2.upstream.cache.CacheWriter
;
import
com.google.android.exoplayer2.upstream.cache.CacheWriter
;
import
com.google.android.exoplayer2.upstream.cache.ContentMetadata
;
import
com.google.android.exoplayer2.upstream.cache.ContentMetadata
;
import
com.google.android.exoplayer2.util.Assertions
;
import
com.google.android.exoplayer2.util.Assertions
;
import
com.google.android.exoplayer2.util.Log
;
import
com.google.android.exoplayer2.util.PriorityTaskManager
;
import
com.google.android.exoplayer2.util.PriorityTaskManager
;
import
com.google.android.exoplayer2.util.PriorityTaskManager.PriorityTooLowException
;
import
com.google.android.exoplayer2.util.PriorityTaskManager.PriorityTooLowException
;
import
com.google.android.exoplayer2.util.SystemClock
;
import
com.google.android.exoplayer2.util.Util
;
import
com.google.android.exoplayer2.util.Util
;
import
java.io.IOException
;
import
java.io.IOException
;
import
java.util.ArrayList
;
import
java.util.ArrayList
;
...
@@ -218,17 +220,23 @@ public abstract class SegmentDownloader<M extends FilterableManifest<M>> impleme
...
@@ -218,17 +220,23 @@ public abstract class SegmentDownloader<M extends FilterableManifest<M>> impleme
}
}
}
}
long
timer
=
0
;
@Override
@Override
public
final
void
remove
()
{
public
final
void
remove
()
{
Cache
cache
=
Assertions
.
checkNotNull
(
cacheDataSourceFactory
.
getCache
());
Cache
cache
=
Assertions
.
checkNotNull
(
cacheDataSourceFactory
.
getCache
());
CacheKeyFactory
cacheKeyFactory
=
cacheDataSourceFactory
.
getCacheKeyFactory
();
CacheKeyFactory
cacheKeyFactory
=
cacheDataSourceFactory
.
getCacheKeyFactory
();
CacheDataSource
dataSource
=
cacheDataSourceFactory
.
createDataSourceForRemovingDownload
();
CacheDataSource
dataSource
=
cacheDataSourceFactory
.
createDataSourceForRemovingDownload
();
try
{
try
{
timer
=
SystemClock
.
DEFAULT
.
elapsedRealtime
();
M
manifest
=
getManifest
(
dataSource
,
manifestDataSpec
);
M
manifest
=
getManifest
(
dataSource
,
manifestDataSpec
);
Log
.
e
(
"XXX"
,
"E1\t"
+
(
SystemClock
.
DEFAULT
.
elapsedRealtime
()
-
timer
));
timer
=
SystemClock
.
DEFAULT
.
elapsedRealtime
();
List
<
Segment
>
segments
=
getSegments
(
dataSource
,
manifest
,
true
);
List
<
Segment
>
segments
=
getSegments
(
dataSource
,
manifest
,
true
);
for
(
int
i
=
0
;
i
<
segments
.
size
();
i
++)
{
for
(
int
i
=
0
;
i
<
segments
.
size
();
i
++)
{
cache
.
removeResource
(
cacheKeyFactory
.
buildCacheKey
(
segments
.
get
(
i
).
dataSpec
));
cache
.
removeResource
(
cacheKeyFactory
.
buildCacheKey
(
segments
.
get
(
i
).
dataSpec
));
}
}
Log
.
e
(
"XXX"
,
"E2\t"
+
(
SystemClock
.
DEFAULT
.
elapsedRealtime
()
-
timer
));
}
catch
(
IOException
e
)
{
}
catch
(
IOException
e
)
{
// Ignore exceptions when removing.
// Ignore exceptions when removing.
}
finally
{
}
finally
{
...
...
library/core/src/main/java/com/google/android/exoplayer2/upstream/cache/Cache.java
View file @
235df090
...
@@ -165,7 +165,7 @@ public interface Cache {
...
@@ -165,7 +165,7 @@ public interface Cache {
* defines the file in which the data is stored. {@link CacheSpan#isCached} is true. The caller
* defines the file in which the data is stored. {@link CacheSpan#isCached} is true. The caller
* may read from the cache file, but does not acquire any locks.
* may read from the cache file, but does not acquire any locks.
*
*
* <p>If there is no cache entry overlapping {@code
offset
}, then the returned {@link CacheSpan}
* <p>If there is no cache entry overlapping {@code
position
}, then the returned {@link CacheSpan}
* defines a hole in the cache starting at {@code position} into which the caller may write as it
* defines a hole in the cache starting at {@code position} into which the caller may write as it
* obtains the data from some other source. The returned {@link CacheSpan} serves as a lock.
* obtains the data from some other source. The returned {@link CacheSpan} serves as a lock.
* Whilst the caller holds the lock it may write data into the hole. It may split data into
* Whilst the caller holds the lock it may write data into the hole. It may split data into
...
@@ -177,31 +177,40 @@ public interface Cache {
...
@@ -177,31 +177,40 @@ public interface Cache {
*
*
* @param key The cache key of the resource.
* @param key The cache key of the resource.
* @param position The starting position in the resource from which data is required.
* @param position The starting position in the resource from which data is required.
* @param length The length of the data being requested, or {@link C#LENGTH_UNSET} if unbounded.
* The length is ignored in the case of a cache hit. In the case of a cache miss, it defines
* the maximum length of the hole {@link CacheSpan} that's returned. Cache implementations may
* support parallel writes into non-overlapping holes, and so passing the actual required
* length should be preferred to passing {@link C#LENGTH_UNSET} when possible.
* @return The {@link CacheSpan}.
* @return The {@link CacheSpan}.
* @throws InterruptedException If the thread was interrupted.
* @throws InterruptedException If the thread was interrupted.
* @throws CacheException If an error is encountered.
* @throws CacheException If an error is encountered.
*/
*/
@WorkerThread
@WorkerThread
CacheSpan
startReadWrite
(
String
key
,
long
position
)
throws
InterruptedException
,
CacheException
;
CacheSpan
startReadWrite
(
String
key
,
long
position
,
long
length
)
throws
InterruptedException
,
CacheException
;
/**
/**
* Same as {@link #startReadWrite(String, long
)}. However, if the cache entry is locked, then
* Same as {@link #startReadWrite(String, long
, long)}. However, if the cache entry is locked,
* instead of blocking, this method will return null as the {@link CacheSpan}.
*
then
instead of blocking, this method will return null as the {@link CacheSpan}.
*
*
* <p>This method may be slow and shouldn't normally be called on the main thread.
* <p>This method may be slow and shouldn't normally be called on the main thread.
*
*
* @param key The cache key of the resource.
* @param key The cache key of the resource.
* @param position The starting position in the resource from which data is required.
* @param position The starting position in the resource from which data is required.
* @param length The length of the data being requested, or {@link C#LENGTH_UNSET} if unbounded.
* The length is ignored in the case of a cache hit. In the case of a cache miss, it defines
* the range of data locked by the returned {@link CacheSpan}.
* @return The {@link CacheSpan}. Or null if the cache entry is locked.
* @return The {@link CacheSpan}. Or null if the cache entry is locked.
* @throws CacheException If an error is encountered.
* @throws CacheException If an error is encountered.
*/
*/
@WorkerThread
@WorkerThread
@Nullable
@Nullable
CacheSpan
startReadWriteNonBlocking
(
String
key
,
long
position
)
throws
CacheException
;
CacheSpan
startReadWriteNonBlocking
(
String
key
,
long
position
,
long
length
)
throws
CacheException
;
/**
/**
* Obtains a cache file into which data can be written. Must only be called when holding a
* Obtains a cache file into which data can be written. Must only be called when holding a
* corresponding hole {@link CacheSpan} obtained from {@link #startReadWrite(String, long)}.
* corresponding hole {@link CacheSpan} obtained from {@link #startReadWrite(String, long
, long
)}.
*
*
* <p>This method may be slow and shouldn't normally be called on the main thread.
* <p>This method may be slow and shouldn't normally be called on the main thread.
*
*
...
@@ -217,7 +226,7 @@ public interface Cache {
...
@@ -217,7 +226,7 @@ public interface Cache {
/**
/**
* Commits a file into the cache. Must only be called when holding a corresponding hole {@link
* Commits a file into the cache. Must only be called when holding a corresponding hole {@link
* CacheSpan} obtained from {@link #startReadWrite(String, long)}.
* CacheSpan} obtained from {@link #startReadWrite(String, long
, long
)}.
*
*
* <p>This method may be slow and shouldn't normally be called on the main thread.
* <p>This method may be slow and shouldn't normally be called on the main thread.
*
*
...
@@ -229,7 +238,7 @@ public interface Cache {
...
@@ -229,7 +238,7 @@ public interface Cache {
void
commitFile
(
File
file
,
long
length
)
throws
CacheException
;
void
commitFile
(
File
file
,
long
length
)
throws
CacheException
;
/**
/**
* Releases a {@link CacheSpan} obtained from {@link #startReadWrite(String, long)} which
* Releases a {@link CacheSpan} obtained from {@link #startReadWrite(String, long
, long
)} which
* corresponded to a hole in the cache.
* corresponded to a hole in the cache.
*
*
* @param holeSpan The {@link CacheSpan} being released.
* @param holeSpan The {@link CacheSpan} being released.
...
...
library/core/src/main/java/com/google/android/exoplayer2/upstream/cache/CacheDataSource.java
View file @
235df090
...
@@ -691,13 +691,13 @@ public final class CacheDataSource implements DataSource {
...
@@ -691,13 +691,13 @@ public final class CacheDataSource implements DataSource {
nextSpan
=
null
;
nextSpan
=
null
;
}
else
if
(
blockOnCache
)
{
}
else
if
(
blockOnCache
)
{
try
{
try
{
nextSpan
=
cache
.
startReadWrite
(
key
,
readPosition
);
nextSpan
=
cache
.
startReadWrite
(
key
,
readPosition
,
bytesRemaining
);
}
catch
(
InterruptedException
e
)
{
}
catch
(
InterruptedException
e
)
{
Thread
.
currentThread
().
interrupt
();
Thread
.
currentThread
().
interrupt
();
throw
new
InterruptedIOException
();
throw
new
InterruptedIOException
();
}
}
}
else
{
}
else
{
nextSpan
=
cache
.
startReadWriteNonBlocking
(
key
,
readPosition
);
nextSpan
=
cache
.
startReadWriteNonBlocking
(
key
,
readPosition
,
bytesRemaining
);
}
}
DataSpec
nextDataSpec
;
DataSpec
nextDataSpec
;
...
...
library/core/src/main/java/com/google/android/exoplayer2/upstream/cache/CacheSpan.java
View file @
235df090
...
@@ -98,4 +98,8 @@ public class CacheSpan implements Comparable<CacheSpan> {
...
@@ -98,4 +98,8 @@ public class CacheSpan implements Comparable<CacheSpan> {
return
startOffsetDiff
==
0
?
0
:
((
startOffsetDiff
<
0
)
?
-
1
:
1
);
return
startOffsetDiff
==
0
?
0
:
((
startOffsetDiff
<
0
)
?
-
1
:
1
);
}
}
@Override
public
String
toString
()
{
return
"["
+
position
+
", "
+
length
+
"]"
;
}
}
}
library/core/src/main/java/com/google/android/exoplayer2/upstream/cache/CachedContent.java
View file @
235df090
...
@@ -19,8 +19,10 @@ import static com.google.android.exoplayer2.util.Assertions.checkArgument;
...
@@ -19,8 +19,10 @@ import static com.google.android.exoplayer2.util.Assertions.checkArgument;
import
static
com
.
google
.
android
.
exoplayer2
.
util
.
Assertions
.
checkState
;
import
static
com
.
google
.
android
.
exoplayer2
.
util
.
Assertions
.
checkState
;
import
androidx.annotation.Nullable
;
import
androidx.annotation.Nullable
;
import
com.google.android.exoplayer2.C
;
import
com.google.android.exoplayer2.util.Log
;
import
com.google.android.exoplayer2.util.Log
;
import
java.io.File
;
import
java.io.File
;
import
java.util.ArrayList
;
import
java.util.TreeSet
;
import
java.util.TreeSet
;
/** Defines the cached content for a single resource. */
/** Defines the cached content for a single resource. */
...
@@ -34,10 +36,11 @@ import java.util.TreeSet;
...
@@ -34,10 +36,11 @@ import java.util.TreeSet;
public
final
String
key
;
public
final
String
key
;
/** The cached spans of this content. */
/** The cached spans of this content. */
private
final
TreeSet
<
SimpleCacheSpan
>
cachedSpans
;
private
final
TreeSet
<
SimpleCacheSpan
>
cachedSpans
;
/** Currently locked ranges. */
private
final
ArrayList
<
Range
>
lockedRanges
;
/** Metadata values. */
/** Metadata values. */
private
DefaultContentMetadata
metadata
;
private
DefaultContentMetadata
metadata
;
/** Whether the content is locked. */
private
boolean
locked
;
/**
/**
* Creates a CachedContent.
* Creates a CachedContent.
...
@@ -53,7 +56,8 @@ import java.util.TreeSet;
...
@@ -53,7 +56,8 @@ import java.util.TreeSet;
this
.
id
=
id
;
this
.
id
=
id
;
this
.
key
=
key
;
this
.
key
=
key
;
this
.
metadata
=
metadata
;
this
.
metadata
=
metadata
;
this
.
cachedSpans
=
new
TreeSet
<>();
cachedSpans
=
new
TreeSet
<>();
lockedRanges
=
new
ArrayList
<>();
}
}
/** Returns the metadata. */
/** Returns the metadata. */
...
@@ -72,14 +76,58 @@ import java.util.TreeSet;
...
@@ -72,14 +76,58 @@ import java.util.TreeSet;
return
!
metadata
.
equals
(
oldMetadata
);
return
!
metadata
.
equals
(
oldMetadata
);
}
}
/** Returns whether the content is locked. */
/** Returns whether the entire resource is fully unlocked. */
public
boolean
isLocked
()
{
public
boolean
isFullyUnlocked
()
{
return
locked
;
return
lockedRanges
.
isEmpty
();
}
/**
* Returns whether the specified range of the resource is fully locked by a single lock.
*
* @param position The position of the range.
* @param length The length of the range, or {@link C#LENGTH_UNSET} if unbounded.
* @return Whether the range is fully locked by a single lock.
*/
public
boolean
isFullyLocked
(
long
position
,
long
length
)
{
for
(
int
i
=
0
;
i
<
lockedRanges
.
size
();
i
++)
{
if
(
lockedRanges
.
get
(
i
).
contains
(
position
,
length
))
{
return
true
;
}
}
return
false
;
}
}
/** Sets the locked state of the content. */
/**
public
void
setLocked
(
boolean
locked
)
{
* Attempts to lock the specified range of the resource.
this
.
locked
=
locked
;
*
* @param position The position of the range.
* @param length The length of the range, or {@link C#LENGTH_UNSET} if unbounded.
* @return Whether the range was successfully locked.
*/
public
boolean
lockRange
(
long
position
,
long
length
)
{
for
(
int
i
=
0
;
i
<
lockedRanges
.
size
();
i
++)
{
if
(
lockedRanges
.
get
(
i
).
intersects
(
position
,
length
))
{
return
false
;
}
}
lockedRanges
.
add
(
new
Range
(
position
,
length
));
return
true
;
}
/**
* Unlocks the currently locked range starting at the specified position.
*
* @param position The starting position of the locked range.
* @throws IllegalStateException If there was no locked range starting at the specified position.
*/
public
void
unlockRange
(
long
position
)
{
for
(
int
i
=
0
;
i
<
lockedRanges
.
size
();
i
++)
{
if
(
lockedRanges
.
get
(
i
).
position
==
position
)
{
lockedRanges
.
remove
(
i
);
return
;
}
}
throw
new
IllegalStateException
();
}
}
/** Adds the given {@link SimpleCacheSpan} which contains a part of the content. */
/** Adds the given {@link SimpleCacheSpan} which contains a part of the content. */
...
@@ -93,18 +141,25 @@ import java.util.TreeSet;
...
@@ -93,18 +141,25 @@ import java.util.TreeSet;
}
}
/**
/**
* Returns the span containing the position. If there isn't one, it returns a hole span
* Returns the cache span corresponding to the provided range. See {@link
* which defines the maximum extents of the hole in the cache.
* Cache#startReadWrite(String, long, long)} for detailed descriptions of the returned spans.
*
* @param position The position of the span being requested.
* @param length The length of the span, or {@link C#LENGTH_UNSET} if unbounded.
* @return The corresponding cache {@link SimpleCacheSpan}.
*/
*/
public
SimpleCacheSpan
getSpan
(
long
position
)
{
public
SimpleCacheSpan
getSpan
(
long
position
,
long
length
)
{
SimpleCacheSpan
lookupSpan
=
SimpleCacheSpan
.
createLookup
(
key
,
position
);
SimpleCacheSpan
lookupSpan
=
SimpleCacheSpan
.
createLookup
(
key
,
position
);
SimpleCacheSpan
floorSpan
=
cachedSpans
.
floor
(
lookupSpan
);
SimpleCacheSpan
floorSpan
=
cachedSpans
.
floor
(
lookupSpan
);
if
(
floorSpan
!=
null
&&
floorSpan
.
position
+
floorSpan
.
length
>
position
)
{
if
(
floorSpan
!=
null
&&
floorSpan
.
position
+
floorSpan
.
length
>
position
)
{
return
floorSpan
;
return
floorSpan
;
}
}
SimpleCacheSpan
ceilSpan
=
cachedSpans
.
ceiling
(
lookupSpan
);
SimpleCacheSpan
ceilSpan
=
cachedSpans
.
ceiling
(
lookupSpan
);
return
ceilSpan
==
null
?
SimpleCacheSpan
.
createOpenHole
(
key
,
position
)
if
(
ceilSpan
!=
null
)
{
:
SimpleCacheSpan
.
createClosedHole
(
key
,
position
,
ceilSpan
.
position
-
position
);
long
holeLength
=
ceilSpan
.
position
-
position
;
length
=
length
==
C
.
LENGTH_UNSET
?
holeLength
:
Math
.
min
(
holeLength
,
length
);
}
return
SimpleCacheSpan
.
createHole
(
key
,
position
,
length
);
}
}
/**
/**
...
@@ -121,7 +176,7 @@ import java.util.TreeSet;
...
@@ -121,7 +176,7 @@ import java.util.TreeSet;
public
long
getCachedBytesLength
(
long
position
,
long
length
)
{
public
long
getCachedBytesLength
(
long
position
,
long
length
)
{
checkArgument
(
position
>=
0
);
checkArgument
(
position
>=
0
);
checkArgument
(
length
>=
0
);
checkArgument
(
length
>=
0
);
SimpleCacheSpan
span
=
getSpan
(
position
);
SimpleCacheSpan
span
=
getSpan
(
position
,
length
);
if
(
span
.
isHoleSpan
())
{
if
(
span
.
isHoleSpan
())
{
// We don't have a span covering the start of the queried region.
// We don't have a span covering the start of the queried region.
return
-
Math
.
min
(
span
.
isOpenEnded
()
?
Long
.
MAX_VALUE
:
span
.
length
,
length
);
return
-
Math
.
min
(
span
.
isOpenEnded
()
?
Long
.
MAX_VALUE
:
span
.
length
,
length
);
...
@@ -215,4 +270,51 @@ import java.util.TreeSet;
...
@@ -215,4 +270,51 @@ import java.util.TreeSet;
&&
cachedSpans
.
equals
(
that
.
cachedSpans
)
&&
cachedSpans
.
equals
(
that
.
cachedSpans
)
&&
metadata
.
equals
(
that
.
metadata
);
&&
metadata
.
equals
(
that
.
metadata
);
}
}
private
static
final
class
Range
{
/** The starting position of the range. */
public
final
long
position
;
/** The length of the range, or {@link C#LENGTH_UNSET} if unbounded. */
public
final
long
length
;
public
Range
(
long
position
,
long
length
)
{
this
.
position
=
position
;
this
.
length
=
length
;
}
/**
* Returns whether this range fully contains the range specified by {@code otherPosition} and
* {@code otherLength}.
*
* @param otherPosition The position of the range to check.
* @param otherLength The length of the range to check, or {@link C#LENGTH_UNSET} if unbounded.
* @return Whether this range fully contains the specified range.
*/
public
boolean
contains
(
long
otherPosition
,
long
otherLength
)
{
if
(
length
==
C
.
LENGTH_UNSET
)
{
return
otherPosition
>=
position
;
}
else
if
(
otherLength
==
C
.
LENGTH_UNSET
)
{
return
false
;
}
else
{
return
position
<=
otherPosition
&&
(
otherPosition
+
otherLength
)
<=
(
position
+
length
);
}
}
/**
* Returns whether this range intersects with the range specified by {@code otherPosition} and
* {@code otherLength}.
*
* @param otherPosition The position of the range to check.
* @param otherLength The length of the range to check, or {@link C#LENGTH_UNSET} if unbounded.
* @return Whether this range intersects with the specified range.
*/
public
boolean
intersects
(
long
otherPosition
,
long
otherLength
)
{
if
(
position
<=
otherPosition
)
{
return
length
==
C
.
LENGTH_UNSET
||
position
+
length
>
otherPosition
;
}
else
{
return
otherLength
==
C
.
LENGTH_UNSET
||
otherPosition
+
otherLength
>
position
;
}
}
}
}
}
library/core/src/main/java/com/google/android/exoplayer2/upstream/cache/CachedContentIndex.java
View file @
235df090
...
@@ -273,7 +273,7 @@ import org.checkerframework.checker.nullness.compatqual.NullableType;
...
@@ -273,7 +273,7 @@ import org.checkerframework.checker.nullness.compatqual.NullableType;
*/
*/
public
void
maybeRemove
(
String
key
)
{
public
void
maybeRemove
(
String
key
)
{
@Nullable
CachedContent
cachedContent
=
keyToContent
.
get
(
key
);
@Nullable
CachedContent
cachedContent
=
keyToContent
.
get
(
key
);
if
(
cachedContent
!=
null
&&
cachedContent
.
isEmpty
()
&&
!
cachedContent
.
isL
ocked
())
{
if
(
cachedContent
!=
null
&&
cachedContent
.
isEmpty
()
&&
cachedContent
.
isFullyUnl
ocked
())
{
keyToContent
.
remove
(
key
);
keyToContent
.
remove
(
key
);
int
id
=
cachedContent
.
id
;
int
id
=
cachedContent
.
id
;
boolean
neverStored
=
newIds
.
get
(
id
);
boolean
neverStored
=
newIds
.
get
(
id
);
...
...
library/core/src/main/java/com/google/android/exoplayer2/upstream/cache/SimpleCache.java
View file @
235df090
...
@@ -353,13 +353,13 @@ public final class SimpleCache implements Cache {
...
@@ -353,13 +353,13 @@ public final class SimpleCache implements Cache {
}
}
@Override
@Override
public
synchronized
CacheSpan
startReadWrite
(
String
key
,
long
position
)
public
synchronized
CacheSpan
startReadWrite
(
String
key
,
long
position
,
long
length
)
throws
InterruptedException
,
CacheException
{
throws
InterruptedException
,
CacheException
{
Assertions
.
checkState
(!
released
);
Assertions
.
checkState
(!
released
);
checkInitialization
();
checkInitialization
();
while
(
true
)
{
while
(
true
)
{
CacheSpan
span
=
startReadWriteNonBlocking
(
key
,
position
);
CacheSpan
span
=
startReadWriteNonBlocking
(
key
,
position
,
length
);
if
(
span
!=
null
)
{
if
(
span
!=
null
)
{
return
span
;
return
span
;
}
else
{
}
else
{
...
@@ -375,12 +375,12 @@ public final class SimpleCache implements Cache {
...
@@ -375,12 +375,12 @@ public final class SimpleCache implements Cache {
@Override
@Override
@Nullable
@Nullable
public
synchronized
CacheSpan
startReadWriteNonBlocking
(
String
key
,
long
position
)
public
synchronized
CacheSpan
startReadWriteNonBlocking
(
String
key
,
long
position
,
long
length
)
throws
CacheException
{
throws
CacheException
{
Assertions
.
checkState
(!
released
);
Assertions
.
checkState
(!
released
);
checkInitialization
();
checkInitialization
();
SimpleCacheSpan
span
=
getSpan
(
key
,
position
);
SimpleCacheSpan
span
=
getSpan
(
key
,
position
,
length
);
if
(
span
.
isCached
)
{
if
(
span
.
isCached
)
{
// Read case.
// Read case.
...
@@ -388,9 +388,8 @@ public final class SimpleCache implements Cache {
...
@@ -388,9 +388,8 @@ public final class SimpleCache implements Cache {
}
}
CachedContent
cachedContent
=
contentIndex
.
getOrAdd
(
key
);
CachedContent
cachedContent
=
contentIndex
.
getOrAdd
(
key
);
if
(
!
cachedContent
.
isLocked
(
))
{
if
(
cachedContent
.
lockRange
(
position
,
span
.
length
))
{
// Write case.
// Write case.
cachedContent
.
setLocked
(
true
);
return
span
;
return
span
;
}
}
...
@@ -405,7 +404,7 @@ public final class SimpleCache implements Cache {
...
@@ -405,7 +404,7 @@ public final class SimpleCache implements Cache {
CachedContent
cachedContent
=
contentIndex
.
get
(
key
);
CachedContent
cachedContent
=
contentIndex
.
get
(
key
);
Assertions
.
checkNotNull
(
cachedContent
);
Assertions
.
checkNotNull
(
cachedContent
);
Assertions
.
checkState
(
cachedContent
.
is
Locked
(
));
Assertions
.
checkState
(
cachedContent
.
is
FullyLocked
(
position
,
length
));
if
(!
cacheDir
.
exists
())
{
if
(!
cacheDir
.
exists
())
{
// For some reason the cache directory doesn't exist. Make a best effort to create it.
// For some reason the cache directory doesn't exist. Make a best effort to create it.
cacheDir
.
mkdirs
();
cacheDir
.
mkdirs
();
...
@@ -435,7 +434,7 @@ public final class SimpleCache implements Cache {
...
@@ -435,7 +434,7 @@ public final class SimpleCache implements Cache {
SimpleCacheSpan
span
=
SimpleCacheSpan
span
=
Assertions
.
checkNotNull
(
SimpleCacheSpan
.
createCacheEntry
(
file
,
length
,
contentIndex
));
Assertions
.
checkNotNull
(
SimpleCacheSpan
.
createCacheEntry
(
file
,
length
,
contentIndex
));
CachedContent
cachedContent
=
Assertions
.
checkNotNull
(
contentIndex
.
get
(
span
.
key
));
CachedContent
cachedContent
=
Assertions
.
checkNotNull
(
contentIndex
.
get
(
span
.
key
));
Assertions
.
checkState
(
cachedContent
.
is
Locked
(
));
Assertions
.
checkState
(
cachedContent
.
is
FullyLocked
(
span
.
position
,
span
.
length
));
// Check if the span conflicts with the set content length
// Check if the span conflicts with the set content length
long
contentLength
=
ContentMetadata
.
getContentLength
(
cachedContent
.
getMetadata
());
long
contentLength
=
ContentMetadata
.
getContentLength
(
cachedContent
.
getMetadata
());
...
@@ -464,8 +463,7 @@ public final class SimpleCache implements Cache {
...
@@ -464,8 +463,7 @@ public final class SimpleCache implements Cache {
public
synchronized
void
releaseHoleSpan
(
CacheSpan
holeSpan
)
{
public
synchronized
void
releaseHoleSpan
(
CacheSpan
holeSpan
)
{
Assertions
.
checkState
(!
released
);
Assertions
.
checkState
(!
released
);
CachedContent
cachedContent
=
Assertions
.
checkNotNull
(
contentIndex
.
get
(
holeSpan
.
key
));
CachedContent
cachedContent
=
Assertions
.
checkNotNull
(
contentIndex
.
get
(
holeSpan
.
key
));
Assertions
.
checkState
(
cachedContent
.
isLocked
());
cachedContent
.
unlockRange
(
holeSpan
.
position
);
cachedContent
.
setLocked
(
false
);
contentIndex
.
maybeRemove
(
cachedContent
.
key
);
contentIndex
.
maybeRemove
(
cachedContent
.
key
);
notifyAll
();
notifyAll
();
}
}
...
@@ -688,23 +686,21 @@ public final class SimpleCache implements Cache {
...
@@ -688,23 +686,21 @@ public final class SimpleCache implements Cache {
}
}
/**
/**
* Returns the cache span corresponding to the provided lookup span.
* Returns the cache span corresponding to the provided key and range. See {@link
*
* Cache#startReadWrite(String, long, long)} for detailed descriptions of the returned spans.
* <p>If the lookup position is contained by an existing entry in the cache, then the returned
* span defines the file in which the data is stored. If the lookup position is not contained by
* an existing entry, then the returned span defines the maximum extents of the hole in the cache.
*
*
* @param key The key of the span being requested.
* @param key The key of the span being requested.
* @param position The position of the span being requested.
* @param position The position of the span being requested.
* @param length The length of the span, or {@link C#LENGTH_UNSET} if unbounded.
* @return The corresponding cache {@link SimpleCacheSpan}.
* @return The corresponding cache {@link SimpleCacheSpan}.
*/
*/
private
SimpleCacheSpan
getSpan
(
String
key
,
long
position
)
{
private
SimpleCacheSpan
getSpan
(
String
key
,
long
position
,
long
length
)
{
@Nullable
CachedContent
cachedContent
=
contentIndex
.
get
(
key
);
@Nullable
CachedContent
cachedContent
=
contentIndex
.
get
(
key
);
if
(
cachedContent
==
null
)
{
if
(
cachedContent
==
null
)
{
return
SimpleCacheSpan
.
create
OpenHole
(
key
,
position
);
return
SimpleCacheSpan
.
create
Hole
(
key
,
position
,
length
);
}
}
while
(
true
)
{
while
(
true
)
{
SimpleCacheSpan
span
=
cachedContent
.
getSpan
(
position
);
SimpleCacheSpan
span
=
cachedContent
.
getSpan
(
position
,
length
);
if
(
span
.
isCached
&&
span
.
file
.
length
()
!=
span
.
length
)
{
if
(
span
.
isCached
&&
span
.
file
.
length
()
!=
span
.
length
)
{
// The file has been modified or deleted underneath us. It's likely that other files will
// The file has been modified or deleted underneath us. It's likely that other files will
// have been modified too, so scan the whole in-memory representation.
// have been modified too, so scan the whole in-memory representation.
...
...
library/core/src/main/java/com/google/android/exoplayer2/upstream/cache/SimpleCacheSpan.java
View file @
235df090
...
@@ -54,7 +54,7 @@ import java.util.regex.Pattern;
...
@@ -54,7 +54,7 @@ import java.util.regex.Pattern;
* Creates a lookup span.
* Creates a lookup span.
*
*
* @param key The cache key of the resource.
* @param key The cache key of the resource.
* @param position The position of the
{@link CacheSpan}
in the resource.
* @param position The position of the
span
in the resource.
* @return The span.
* @return The span.
*/
*/
public
static
SimpleCacheSpan
createLookup
(
String
key
,
long
position
)
{
public
static
SimpleCacheSpan
createLookup
(
String
key
,
long
position
)
{
...
@@ -62,25 +62,14 @@ import java.util.regex.Pattern;
...
@@ -62,25 +62,14 @@ import java.util.regex.Pattern;
}
}
/**
/**
* Creates a
n open
hole span.
* Creates a hole span.
*
*
* @param key The cache key of the resource.
* @param key The cache key of the resource.
* @param position The position of the {@link CacheSpan} in the resource.
* @param position The position of the span in the resource.
* @return The span.
* @param length The length of the span, or {@link C#LENGTH_UNSET} if unbounded.
*/
* @return The hole span.
public
static
SimpleCacheSpan
createOpenHole
(
String
key
,
long
position
)
{
return
new
SimpleCacheSpan
(
key
,
position
,
C
.
LENGTH_UNSET
,
C
.
TIME_UNSET
,
null
);
}
/**
* Creates a closed hole span.
*
* @param key The cache key of the resource.
* @param position The position of the {@link CacheSpan} in the resource.
* @param length The length of the {@link CacheSpan}.
* @return The span.
*/
*/
public
static
SimpleCacheSpan
create
Closed
Hole
(
String
key
,
long
position
,
long
length
)
{
public
static
SimpleCacheSpan
createHole
(
String
key
,
long
position
,
long
length
)
{
return
new
SimpleCacheSpan
(
key
,
position
,
length
,
C
.
TIME_UNSET
,
null
);
return
new
SimpleCacheSpan
(
key
,
position
,
length
,
C
.
TIME_UNSET
,
null
);
}
}
...
@@ -191,12 +180,11 @@ import java.util.regex.Pattern;
...
@@ -191,12 +180,11 @@ import java.util.regex.Pattern;
/**
/**
* @param key The cache key of the resource.
* @param key The cache key of the resource.
* @param position The position of the {@link CacheSpan} in the resource.
* @param position The position of the span in the resource.
* @param length The length of the {@link CacheSpan}, or {@link C#LENGTH_UNSET} if this is an
* @param length The length of the span, or {@link C#LENGTH_UNSET} if this is an open-ended hole.
* open-ended hole.
* @param lastTouchTimestamp The last touch timestamp, or {@link C#TIME_UNSET} if {@link
* @param lastTouchTimestamp The last touch timestamp, or {@link C#TIME_UNSET} if {@link
* #isCached} is false.
* #isCached} is false.
* @param file The file corresponding to this
{@link CacheSpan}
, or null if it's a hole.
* @param file The file corresponding to this
span
, or null if it's a hole.
*/
*/
private
SimpleCacheSpan
(
private
SimpleCacheSpan
(
String
key
,
long
position
,
long
length
,
long
lastTouchTimestamp
,
@Nullable
File
file
)
{
String
key
,
long
position
,
long
length
,
long
lastTouchTimestamp
,
@Nullable
File
file
)
{
...
...
library/core/src/test/java/com/google/android/exoplayer2/upstream/cache/CacheDataSourceTest.java
View file @
235df090
...
@@ -384,7 +384,7 @@ public final class CacheDataSourceTest {
...
@@ -384,7 +384,7 @@ public final class CacheDataSourceTest {
.
appendReadData
(
1
);
.
appendReadData
(
1
);
// Lock the content on the cache.
// Lock the content on the cache.
CacheSpan
cacheSpan
=
cache
.
startReadWriteNonBlocking
(
defaultCacheKey
,
0
);
CacheSpan
cacheSpan
=
cache
.
startReadWriteNonBlocking
(
defaultCacheKey
,
0
,
C
.
LENGTH_UNSET
);
assertThat
(
cacheSpan
).
isNotNull
();
assertThat
(
cacheSpan
).
isNotNull
();
assertThat
(
cacheSpan
.
isHoleSpan
()).
isTrue
();
assertThat
(
cacheSpan
.
isHoleSpan
()).
isTrue
();
...
...
library/core/src/test/java/com/google/android/exoplayer2/upstream/cache/CachedContentIndexTest.java
View file @
235df090
...
@@ -301,7 +301,7 @@ public class CachedContentIndexTest {
...
@@ -301,7 +301,7 @@ public class CachedContentIndexTest {
public
void
cantRemoveLockedCachedContent
()
{
public
void
cantRemoveLockedCachedContent
()
{
CachedContentIndex
index
=
newInstance
();
CachedContentIndex
index
=
newInstance
();
CachedContent
cachedContent
=
index
.
getOrAdd
(
"key1"
);
CachedContent
cachedContent
=
index
.
getOrAdd
(
"key1"
);
cachedContent
.
setLocked
(
true
);
cachedContent
.
lockRange
(
0
,
1
);
index
.
maybeRemove
(
cachedContent
.
key
);
index
.
maybeRemove
(
cachedContent
.
key
);
...
...
library/core/src/test/java/com/google/android/exoplayer2/upstream/cache/SimpleCacheTest.java
View file @
235df090
This diff is collapsed.
Click to expand it.
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment