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
f8824ac3
authored
Sep 01, 2015
by
Oliver Woodman
Browse files
Options
_('Browse Files')
Download
Email Patches
Plain Diff
Support dynamic TimeRange for DASH live.
parent
d5f8d1a1
Hide whitespace changes
Inline
Side-by-side
Showing
4 changed files
with
192 additions
and
108 deletions
demo/src/main/java/com/google/android/exoplayer/demo/EventLogger.java
library/src/androidTest/java/com/google/android/exoplayer/TimeRangeTest.java
library/src/main/java/com/google/android/exoplayer/TimeRange.java
library/src/main/java/com/google/android/exoplayer/dash/DashChunkSource.java
demo/src/main/java/com/google/android/exoplayer/demo/EventLogger.java
View file @
f8824ac3
...
@@ -173,8 +173,8 @@ public class EventLogger implements DemoPlayer.Listener, DemoPlayer.InfoListener
...
@@ -173,8 +173,8 @@ public class EventLogger implements DemoPlayer.Listener, DemoPlayer.InfoListener
@Override
@Override
public
void
onAvailableRangeChanged
(
TimeRange
availableRange
)
{
public
void
onAvailableRangeChanged
(
TimeRange
availableRange
)
{
availableRangeValuesUs
=
availableRange
.
getCurrentBoundsUs
(
availableRangeValuesUs
);
availableRangeValuesUs
=
availableRange
.
getCurrentBoundsUs
(
availableRangeValuesUs
);
Log
.
d
(
TAG
,
"availableRange [
"
+
availableRange
.
type
+
", "
+
availableRangeValuesUs
[
0
]
+
", "
Log
.
d
(
TAG
,
"availableRange [
"
+
availableRange
.
isStatic
()
+
", "
+
availableRangeValuesUs
[
0
]
+
availableRangeValuesUs
[
1
]
+
"]"
);
+
", "
+
availableRangeValuesUs
[
1
]
+
"]"
);
}
}
private
void
printInternalError
(
String
type
,
Exception
e
)
{
private
void
printInternalError
(
String
type
,
Exception
e
)
{
...
...
library/src/androidTest/java/com/google/android/exoplayer/TimeRangeTest.java
View file @
f8824ac3
...
@@ -15,6 +15,8 @@
...
@@ -15,6 +15,8 @@
*/
*/
package
com
.
google
.
android
.
exoplayer
;
package
com
.
google
.
android
.
exoplayer
;
import
com.google.android.exoplayer.TimeRange.StaticTimeRange
;
import
junit.framework.TestCase
;
import
junit.framework.TestCase
;
/**
/**
...
@@ -22,14 +24,14 @@ import junit.framework.TestCase;
...
@@ -22,14 +24,14 @@ import junit.framework.TestCase;
*/
*/
public
class
TimeRangeTest
extends
TestCase
{
public
class
TimeRangeTest
extends
TestCase
{
public
void
testEquals
()
{
public
void
test
Static
Equals
()
{
TimeRange
timeRange1
=
new
TimeRange
(
TimeRange
.
TYPE_SNAPSHOT
,
0
,
30000000
);
TimeRange
timeRange1
=
new
StaticTimeRange
(
0
,
30000000
);
assertTrue
(
timeRange1
.
equals
(
timeRange1
));
assertTrue
(
timeRange1
.
equals
(
timeRange1
));
TimeRange
timeRange2
=
new
TimeRange
(
TimeRange
.
TYPE_SNAPSHOT
,
0
,
30000000
);
TimeRange
timeRange2
=
new
StaticTimeRange
(
0
,
30000000
);
assertTrue
(
timeRange1
.
equals
(
timeRange2
));
assertTrue
(
timeRange1
.
equals
(
timeRange2
));
TimeRange
timeRange3
=
new
TimeRange
(
TimeRange
.
TYPE_SNAPSHOT
,
0
,
60000000
);
TimeRange
timeRange3
=
new
StaticTimeRange
(
0
,
60000000
);
assertFalse
(
timeRange1
.
equals
(
timeRange3
));
assertFalse
(
timeRange1
.
equals
(
timeRange3
));
}
}
...
...
library/src/main/java/com/google/android/exoplayer/TimeRange.java
View file @
f8824ac3
...
@@ -15,37 +15,22 @@
...
@@ -15,37 +15,22 @@
*/
*/
package
com
.
google
.
android
.
exoplayer
;
package
com
.
google
.
android
.
exoplayer
;
import
com.google.android.exoplayer.util.Clock
;
import
android.os.SystemClock
;
/**
/**
* A container to store a start and end time in microseconds.
* A container to store a start and end time in microseconds.
*/
*/
public
final
class
TimeRange
{
public
interface
TimeRange
{
/**
* Represents a range of time whose bounds change in bulk increments rather than smoothly over
* time.
*/
public
static
final
int
TYPE_SNAPSHOT
=
0
;
/**
/**
* The type of this time range.
* Whether the range is static, meaning repeated calls to {@link #getCurrentBoundsMs(long[])}
*/
* or {@link #getCurrentBoundsUs(long[])} will return identical results.
public
final
int
type
;
private
final
long
startTimeUs
;
private
final
long
endTimeUs
;
/**
* Create a new {@link TimeRange} of the appropriate type.
*
*
* @param type The type of the TimeRange.
* @return Whether the range is static.
* @param startTimeUs The beginning of the TimeRange.
* @param endTimeUs The end of the TimeRange.
*/
*/
public
TimeRange
(
int
type
,
long
startTimeUs
,
long
endTimeUs
)
{
public
boolean
isStatic
();
this
.
type
=
type
;
this
.
startTimeUs
=
startTimeUs
;
this
.
endTimeUs
=
endTimeUs
;
}
/**
/**
* Returns the start and end times (in milliseconds) of the TimeRange in the provided array,
* Returns the start and end times (in milliseconds) of the TimeRange in the provided array,
...
@@ -54,12 +39,7 @@ public final class TimeRange {
...
@@ -54,12 +39,7 @@ public final class TimeRange {
* @param out An array to store the start and end times; can be null.
* @param out An array to store the start and end times; can be null.
* @return An array containing the start time (index 0) and end time (index 1) in milliseconds.
* @return An array containing the start time (index 0) and end time (index 1) in milliseconds.
*/
*/
public
long
[]
getCurrentBoundsMs
(
long
[]
out
)
{
public
long
[]
getCurrentBoundsMs
(
long
[]
out
);
out
=
getCurrentBoundsUs
(
out
);
out
[
0
]
/=
1000
;
out
[
1
]
/=
1000
;
return
out
;
}
/**
/**
* Returns the start and end times (in microseconds) of the TimeRange in the provided array,
* Returns the start and end times (in microseconds) of the TimeRange in the provided array,
...
@@ -68,35 +48,156 @@ public final class TimeRange {
...
@@ -68,35 +48,156 @@ public final class TimeRange {
* @param out An array to store the start and end times; can be null.
* @param out An array to store the start and end times; can be null.
* @return An array containing the start time (index 0) and end time (index 1) in microseconds.
* @return An array containing the start time (index 0) and end time (index 1) in microseconds.
*/
*/
public
long
[]
getCurrentBoundsUs
(
long
[]
out
)
{
public
long
[]
getCurrentBoundsUs
(
long
[]
out
);
if
(
out
==
null
||
out
.
length
<
2
)
{
out
=
new
long
[
2
];
/**
* A static {@link TimeRange}.
*/
public
static
final
class
StaticTimeRange
implements
TimeRange
{
private
final
long
startTimeUs
;
private
final
long
endTimeUs
;
/**
* @param startTimeUs The beginning of the range.
* @param endTimeUs The end of the range.
*/
public
StaticTimeRange
(
long
startTimeUs
,
long
endTimeUs
)
{
this
.
startTimeUs
=
startTimeUs
;
this
.
endTimeUs
=
endTimeUs
;
}
@Override
public
boolean
isStatic
()
{
return
true
;
}
@Override
public
long
[]
getCurrentBoundsMs
(
long
[]
out
)
{
out
=
getCurrentBoundsUs
(
out
);
out
[
0
]
/=
1000
;
out
[
1
]
/=
1000
;
return
out
;
}
@Override
public
long
[]
getCurrentBoundsUs
(
long
[]
out
)
{
if
(
out
==
null
||
out
.
length
<
2
)
{
out
=
new
long
[
2
];
}
out
[
0
]
=
startTimeUs
;
out
[
1
]
=
endTimeUs
;
return
out
;
}
@Override
public
int
hashCode
()
{
int
result
=
17
;
result
=
31
*
result
+
(
int
)
startTimeUs
;
result
=
31
*
result
+
(
int
)
endTimeUs
;
return
result
;
}
@Override
public
boolean
equals
(
Object
obj
)
{
if
(
obj
==
this
)
{
return
true
;
}
if
(
obj
==
null
||
getClass
()
!=
obj
.
getClass
())
{
return
false
;
}
StaticTimeRange
other
=
(
StaticTimeRange
)
obj
;
return
other
.
startTimeUs
==
startTimeUs
&&
other
.
endTimeUs
==
endTimeUs
;
}
}
out
[
0
]
=
startTimeUs
;
out
[
1
]
=
endTimeUs
;
return
out
;
}
@Override
public
int
hashCode
()
{
int
hashCode
=
0
;
hashCode
|=
type
<<
30
;
hashCode
|=
(((
startTimeUs
+
endTimeUs
)
/
1000
)
&
0x3FFFFFFF
);
return
hashCode
;
}
}
@Override
/**
public
boolean
equals
(
Object
other
)
{
* A dynamic {@link TimeRange}.
if
(
other
==
this
)
{
*/
return
true
;
public
static
final
class
DynamicTimeRange
implements
TimeRange
{
private
final
long
minStartTimeUs
;
private
final
long
maxEndTimeUs
;
private
final
long
elapsedRealtimeAtStartUs
;
private
final
long
bufferDepthUs
;
private
final
Clock
systemClock
;
/**
* @param minStartTimeUs A lower bound on the beginning of the range.
* @param maxEndTimeUs An upper bound on the end of the range.
* @param elapsedRealtimeAtStartUs The value of {@link SystemClock#elapsedRealtime()},
* multiplied by 1000, corresponding to a media time of zero.
* @param bufferDepthUs The buffer depth of the media, or -1.
* @param systemClock A system clock.
*/
public
DynamicTimeRange
(
long
minStartTimeUs
,
long
maxEndTimeUs
,
long
elapsedRealtimeAtStartUs
,
long
bufferDepthUs
,
Clock
systemClock
)
{
this
.
minStartTimeUs
=
minStartTimeUs
;
this
.
maxEndTimeUs
=
maxEndTimeUs
;
this
.
elapsedRealtimeAtStartUs
=
elapsedRealtimeAtStartUs
;
this
.
bufferDepthUs
=
bufferDepthUs
;
this
.
systemClock
=
systemClock
;
}
}
if
(
other
instanceof
TimeRange
)
{
TimeRange
otherTimeRange
=
(
TimeRange
)
other
;
@Override
return
(
otherTimeRange
.
type
==
type
)
&&
(
otherTimeRange
.
startTimeUs
==
startTimeUs
)
public
boolean
isStatic
()
{
&&
(
otherTimeRange
.
endTimeUs
==
endTimeUs
);
}
else
{
return
false
;
return
false
;
}
}
@Override
public
long
[]
getCurrentBoundsMs
(
long
[]
out
)
{
out
=
getCurrentBoundsUs
(
out
);
out
[
0
]
/=
1000
;
out
[
1
]
/=
1000
;
return
out
;
}
@Override
public
long
[]
getCurrentBoundsUs
(
long
[]
out
)
{
if
(
out
==
null
||
out
.
length
<
2
)
{
out
=
new
long
[
2
];
}
// Don't allow the end time to be greater than the total elapsed time.
long
currentEndTimeUs
=
Math
.
min
(
maxEndTimeUs
,
(
systemClock
.
elapsedRealtime
()
*
1000
)
-
elapsedRealtimeAtStartUs
);
long
currentStartTimeUs
=
minStartTimeUs
;
if
(
bufferDepthUs
!=
-
1
)
{
// Don't allow the start time to be less than the current end time minus the buffer depth.
currentStartTimeUs
=
Math
.
max
(
currentStartTimeUs
,
currentEndTimeUs
-
bufferDepthUs
);
}
out
[
0
]
=
currentStartTimeUs
;
out
[
1
]
=
currentEndTimeUs
;
return
out
;
}
@Override
public
int
hashCode
()
{
int
result
=
17
;
result
=
31
*
result
+
(
int
)
minStartTimeUs
;
result
=
31
*
result
+
(
int
)
maxEndTimeUs
;
result
=
31
*
result
+
(
int
)
elapsedRealtimeAtStartUs
;
result
=
31
*
result
+
(
int
)
bufferDepthUs
;
return
result
;
}
@Override
public
boolean
equals
(
Object
obj
)
{
if
(
obj
==
this
)
{
return
true
;
}
if
(
obj
==
null
||
getClass
()
!=
obj
.
getClass
())
{
return
false
;
}
DynamicTimeRange
other
=
(
DynamicTimeRange
)
obj
;
return
other
.
minStartTimeUs
==
minStartTimeUs
&&
other
.
maxEndTimeUs
==
maxEndTimeUs
&&
other
.
elapsedRealtimeAtStartUs
==
elapsedRealtimeAtStartUs
&&
other
.
bufferDepthUs
==
bufferDepthUs
;
}
}
}
}
}
library/src/main/java/com/google/android/exoplayer/dash/DashChunkSource.java
View file @
f8824ac3
...
@@ -18,6 +18,8 @@ package com.google.android.exoplayer.dash;
...
@@ -18,6 +18,8 @@ package com.google.android.exoplayer.dash;
import
com.google.android.exoplayer.BehindLiveWindowException
;
import
com.google.android.exoplayer.BehindLiveWindowException
;
import
com.google.android.exoplayer.MediaFormat
;
import
com.google.android.exoplayer.MediaFormat
;
import
com.google.android.exoplayer.TimeRange
;
import
com.google.android.exoplayer.TimeRange
;
import
com.google.android.exoplayer.TimeRange.DynamicTimeRange
;
import
com.google.android.exoplayer.TimeRange.StaticTimeRange
;
import
com.google.android.exoplayer.TrackRenderer
;
import
com.google.android.exoplayer.TrackRenderer
;
import
com.google.android.exoplayer.chunk.Chunk
;
import
com.google.android.exoplayer.chunk.Chunk
;
import
com.google.android.exoplayer.chunk.ChunkExtractorWrapper
;
import
com.google.android.exoplayer.chunk.ChunkExtractorWrapper
;
...
@@ -115,6 +117,7 @@ public class DashChunkSource implements ChunkSource {
...
@@ -115,6 +117,7 @@ public class DashChunkSource implements ChunkSource {
private
final
long
elapsedRealtimeOffsetUs
;
private
final
long
elapsedRealtimeOffsetUs
;
private
final
int
maxWidth
;
private
final
int
maxWidth
;
private
final
int
maxHeight
;
private
final
int
maxHeight
;
private
final
long
[]
availableRangeValues
;
private
final
SparseArray
<
PeriodHolder
>
periodHolders
;
private
final
SparseArray
<
PeriodHolder
>
periodHolders
;
...
@@ -128,7 +131,6 @@ public class DashChunkSource implements ChunkSource {
...
@@ -128,7 +131,6 @@ public class DashChunkSource implements ChunkSource {
private
DrmInitData
drmInitData
;
private
DrmInitData
drmInitData
;
private
TimeRange
availableRange
;
private
TimeRange
availableRange
;
private
long
[]
availableRangeValues
;
private
boolean
startAtLiveEdge
;
private
boolean
startAtLiveEdge
;
private
boolean
lastChunkWasInitialization
;
private
boolean
lastChunkWasInitialization
;
...
@@ -327,7 +329,6 @@ public class DashChunkSource implements ChunkSource {
...
@@ -327,7 +329,6 @@ public class DashChunkSource implements ChunkSource {
if
(
manifestFetcher
!=
null
)
{
if
(
manifestFetcher
!=
null
)
{
manifestFetcher
.
enable
();
manifestFetcher
.
enable
();
}
}
updateAvailableBounds
(
getNowUs
());
}
}
@Override
@Override
...
@@ -348,7 +349,6 @@ public class DashChunkSource implements ChunkSource {
...
@@ -348,7 +349,6 @@ public class DashChunkSource implements ChunkSource {
MediaPresentationDescription
newManifest
=
manifestFetcher
.
getManifest
();
MediaPresentationDescription
newManifest
=
manifestFetcher
.
getManifest
();
if
(
currentManifest
!=
newManifest
&&
newManifest
!=
null
)
{
if
(
currentManifest
!=
newManifest
&&
newManifest
!=
null
)
{
processManifest
(
newManifest
);
processManifest
(
newManifest
);
updateAvailableBounds
(
getNowUs
());
}
}
// TODO: This is a temporary hack to avoid constantly refreshing the MPD in cases where
// TODO: This is a temporary hack to avoid constantly refreshing the MPD in cases where
...
@@ -401,19 +401,12 @@ public class DashChunkSource implements ChunkSource {
...
@@ -401,19 +401,12 @@ public class DashChunkSource implements ChunkSource {
// In all cases where we return before instantiating a new chunk, we want out.chunk to be null.
// In all cases where we return before instantiating a new chunk, we want out.chunk to be null.
out
.
chunk
=
null
;
out
.
chunk
=
null
;
if
(
currentManifest
.
dynamic
&&
periodHolders
.
valueAt
(
periodHolders
.
size
()
-
1
).
isIndexUnbounded
())
{
// Manifests with unbounded indexes aren't updated regularly, so we need to update the
// segment bounds before use to ensure that they are accurate to the current time
updateAvailableBounds
(
getNowUs
());
}
availableRangeValues
=
availableRange
.
getCurrentBoundsUs
(
availableRangeValues
);
long
segmentStartTimeUs
;
long
segmentStartTimeUs
;
int
segmentNum
=
-
1
;
int
segmentNum
=
-
1
;
boolean
startingNewPeriod
=
false
;
boolean
startingNewPeriod
=
false
;
PeriodHolder
periodHolder
;
PeriodHolder
periodHolder
;
availableRange
.
getCurrentBoundsUs
(
availableRangeValues
);
if
(
queue
.
isEmpty
())
{
if
(
queue
.
isEmpty
())
{
if
(
currentManifest
.
dynamic
)
{
if
(
currentManifest
.
dynamic
)
{
if
(
startAtLiveEdge
)
{
if
(
startAtLiveEdge
)
{
...
@@ -565,45 +558,6 @@ public class DashChunkSource implements ChunkSource {
...
@@ -565,45 +558,6 @@ public class DashChunkSource implements ChunkSource {
// Do nothing.
// Do nothing.
}
}
private
void
updateAvailableBounds
(
long
nowUs
)
{
PeriodHolder
firstPeriod
=
periodHolders
.
valueAt
(
0
);
long
earliestAvailablePosition
=
firstPeriod
.
getAvailableStartTimeUs
();
PeriodHolder
lastPeriod
=
periodHolders
.
valueAt
(
periodHolders
.
size
()
-
1
);
boolean
isManifestUnbounded
=
lastPeriod
.
isIndexUnbounded
();
long
latestAvailablePosition
;
if
(!
currentManifest
.
dynamic
||
!
isManifestUnbounded
)
{
latestAvailablePosition
=
lastPeriod
.
getAvailableEndTimeUs
();
}
else
{
latestAvailablePosition
=
TrackRenderer
.
UNKNOWN_TIME_US
;
}
if
(
currentManifest
.
dynamic
)
{
if
(
isManifestUnbounded
)
{
latestAvailablePosition
=
nowUs
-
currentManifest
.
availabilityStartTime
*
1000
;
}
else
if
(!
lastPeriod
.
isIndexExplicit
())
{
// Some segments defined by the index may not be available yet. Bound the calculated live
// edge based on the elapsed time since the manifest became available.
latestAvailablePosition
=
Math
.
min
(
latestAvailablePosition
,
nowUs
-
currentManifest
.
availabilityStartTime
*
1000
);
}
// if we have a limited timeshift buffer, we need to adjust the earliest seek position so
// that it doesn't start before the buffer
if
(
currentManifest
.
timeShiftBufferDepth
!=
-
1
)
{
long
bufferDepthUs
=
currentManifest
.
timeShiftBufferDepth
*
1000
;
earliestAvailablePosition
=
Math
.
max
(
earliestAvailablePosition
,
latestAvailablePosition
-
bufferDepthUs
);
}
}
TimeRange
newAvailableRange
=
new
TimeRange
(
TimeRange
.
TYPE_SNAPSHOT
,
earliestAvailablePosition
,
latestAvailablePosition
);
if
(
availableRange
==
null
||
!
availableRange
.
equals
(
newAvailableRange
))
{
availableRange
=
newAvailableRange
;
notifyAvailableRangeChanged
(
availableRange
);
}
}
private
static
boolean
mimeTypeIsWebm
(
String
mimeType
)
{
private
static
boolean
mimeTypeIsWebm
(
String
mimeType
)
{
return
mimeType
.
startsWith
(
MimeTypes
.
VIDEO_WEBM
)
||
mimeType
.
startsWith
(
MimeTypes
.
AUDIO_WEBM
);
return
mimeType
.
startsWith
(
MimeTypes
.
VIDEO_WEBM
)
||
mimeType
.
startsWith
(
MimeTypes
.
AUDIO_WEBM
);
}
}
...
@@ -755,9 +709,36 @@ public class DashChunkSource implements ChunkSource {
...
@@ -755,9 +709,36 @@ public class DashChunkSource implements ChunkSource {
periodHolderNextIndex
++;
periodHolderNextIndex
++;
}
}
// Update the available range.
TimeRange
newAvailableRange
=
getAvailableRange
(
getNowUs
());
if
(
availableRange
==
null
||
!
availableRange
.
equals
(
newAvailableRange
))
{
availableRange
=
newAvailableRange
;
notifyAvailableRangeChanged
(
availableRange
);
}
currentManifest
=
manifest
;
currentManifest
=
manifest
;
}
}
private
TimeRange
getAvailableRange
(
long
nowUs
)
{
PeriodHolder
firstPeriod
=
periodHolders
.
valueAt
(
0
);
PeriodHolder
lastPeriod
=
periodHolders
.
valueAt
(
periodHolders
.
size
()
-
1
);
if
(!
currentManifest
.
dynamic
||
lastPeriod
.
isIndexExplicit
())
{
return
new
StaticTimeRange
(
firstPeriod
.
getAvailableStartTimeUs
(),
lastPeriod
.
getAvailableEndTimeUs
());
}
long
minStartPositionUs
=
firstPeriod
.
getAvailableStartTimeUs
();
long
maxEndPositionUs
=
lastPeriod
.
isIndexUnbounded
()
?
Long
.
MAX_VALUE
:
lastPeriod
.
getAvailableEndTimeUs
();
long
elapsedRealtimeAtZeroUs
=
(
systemClock
.
elapsedRealtime
()
*
1000
)
-
(
nowUs
-
currentManifest
.
availabilityStartTime
*
1000
);
long
timeShiftBufferDepthUs
=
currentManifest
.
timeShiftBufferDepth
==
-
1
?
-
1
:
currentManifest
.
timeShiftBufferDepth
*
1000
;
return
new
DynamicTimeRange
(
minStartPositionUs
,
maxEndPositionUs
,
elapsedRealtimeAtZeroUs
,
timeShiftBufferDepthUs
,
systemClock
);
}
private
void
notifyAvailableRangeChanged
(
final
TimeRange
seekRange
)
{
private
void
notifyAvailableRangeChanged
(
final
TimeRange
seekRange
)
{
if
(
eventHandler
!=
null
&&
eventListener
!=
null
)
{
if
(
eventHandler
!=
null
&&
eventListener
!=
null
)
{
eventHandler
.
post
(
new
Runnable
()
{
eventHandler
.
post
(
new
Runnable
()
{
...
...
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