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
709fc773
authored
May 19, 2015
by
Oliver Woodman
Browse files
Options
_('Browse Files')
Download
Email Patches
Plain Diff
First steps toward implementing bounded live seek.
parent
763d68f2
Hide whitespace changes
Inline
Side-by-side
Showing
17 changed files
with
1054 additions
and
259 deletions
RELEASENOTES.md
demo/src/main/java/com/google/android/exoplayer/demo/EventLogger.java
demo/src/main/java/com/google/android/exoplayer/demo/player/DashRendererBuilder.java
demo/src/main/java/com/google/android/exoplayer/demo/player/DebugTrackRenderer.java
demo/src/main/java/com/google/android/exoplayer/demo/player/DemoPlayer.java
demo/src/main/java/com/google/android/exoplayer/demo/player/ExtractorRendererBuilder.java
demo/src/main/java/com/google/android/exoplayer/demo/player/HlsRendererBuilder.java
demo/src/main/java/com/google/android/exoplayer/demo/player/SmoothStreamingRendererBuilder.java
library/src/main/java/com/google/android/exoplayer/TimeRange.java
library/src/main/java/com/google/android/exoplayer/dash/DashChunkSource.java
library/src/main/java/com/google/android/exoplayer/extractor/ts/H264Reader.java
library/src/main/java/com/google/android/exoplayer/extractor/ts/TsExtractor.java
library/src/main/java/com/google/android/exoplayer/upstream/MulticastDataSource.java
library/src/main/java/com/google/android/exoplayer/util/ParsableBitArray.java
library/src/main/java/com/google/android/exoplayer/util/Util.java
library/src/test/java/com/google/android/exoplayer/TimeRangeTest.java
library/src/test/java/com/google/android/exoplayer/dash/DashChunkSourceTest.java
RELEASENOTES.md
View file @
709fc773
# Release notes #
# Release notes #
### r1.3.2 ###
### Current dev branch (from r1.3.2) ###
*
Add option to TsExtractor to allow non-IDR keyframes.
*
Added MulticastDataSource for connecting to multicast streams.
*
(WorkInProgress) - First steps to supporting seeking in DASH DVR window.
### r1.3.2 (from r1.3.1) ###
*
DataSource improvements:
`DefaultUriDataSource`
now handles http://, https://, file://, asset://
*
DataSource improvements:
`DefaultUriDataSource`
now handles http://, https://, file://, asset://
and content:// URIs automatically. It also handles file:///android_asset/
*
URIs, and file paths
and content:// URIs automatically. It also handles file:///android_asset/
*
URIs, and file paths
...
...
demo/src/main/java/com/google/android/exoplayer/demo/EventLogger.java
View file @
709fc773
...
@@ -17,6 +17,7 @@ package com.google.android.exoplayer.demo;
...
@@ -17,6 +17,7 @@ package com.google.android.exoplayer.demo;
import
com.google.android.exoplayer.ExoPlayer
;
import
com.google.android.exoplayer.ExoPlayer
;
import
com.google.android.exoplayer.MediaCodecTrackRenderer.DecoderInitializationException
;
import
com.google.android.exoplayer.MediaCodecTrackRenderer.DecoderInitializationException
;
import
com.google.android.exoplayer.TimeRange
;
import
com.google.android.exoplayer.audio.AudioTrack
;
import
com.google.android.exoplayer.audio.AudioTrack
;
import
com.google.android.exoplayer.chunk.Format
;
import
com.google.android.exoplayer.chunk.Format
;
import
com.google.android.exoplayer.demo.player.DemoPlayer
;
import
com.google.android.exoplayer.demo.player.DemoPlayer
;
...
@@ -46,6 +47,7 @@ public class EventLogger implements DemoPlayer.Listener, DemoPlayer.InfoListener
...
@@ -46,6 +47,7 @@ public class EventLogger implements DemoPlayer.Listener, DemoPlayer.InfoListener
private
long
sessionStartTimeMs
;
private
long
sessionStartTimeMs
;
private
long
[]
loadStartTimeMs
;
private
long
[]
loadStartTimeMs
;
private
long
[]
seekRangeValuesUs
;
public
EventLogger
()
{
public
EventLogger
()
{
loadStartTimeMs
=
new
long
[
DemoPlayer
.
RENDERER_COUNT
];
loadStartTimeMs
=
new
long
[
DemoPlayer
.
RENDERER_COUNT
];
...
@@ -163,7 +165,14 @@ public class EventLogger implements DemoPlayer.Listener, DemoPlayer.InfoListener
...
@@ -163,7 +165,14 @@ public class EventLogger implements DemoPlayer.Listener, DemoPlayer.InfoListener
@Override
@Override
public
void
onDecoderInitialized
(
String
decoderName
,
long
elapsedRealtimeMs
,
public
void
onDecoderInitialized
(
String
decoderName
,
long
elapsedRealtimeMs
,
long
initializationDurationMs
)
{
long
initializationDurationMs
)
{
Log
.
d
(
TAG
,
"decoderInitialized ["
+
getSessionTimeString
()
+
"]"
);
Log
.
d
(
TAG
,
"decoderInitialized ["
+
getSessionTimeString
()
+
", "
+
decoderName
+
"]"
);
}
@Override
public
void
onSeekRangeChanged
(
TimeRange
seekRange
)
{
seekRangeValuesUs
=
seekRange
.
getCurrentBoundsUs
(
seekRangeValuesUs
);
Log
.
d
(
TAG
,
"seekRange [ "
+
seekRange
.
type
+
", "
+
seekRangeValuesUs
[
0
]
+
", "
+
seekRangeValuesUs
[
1
]
+
"]"
);
}
}
private
void
printInternalError
(
String
type
,
Exception
e
)
{
private
void
printInternalError
(
String
type
,
Exception
e
)
{
...
...
demo/src/main/java/com/google/android/exoplayer/demo/player/DashRendererBuilder.java
View file @
709fc773
...
@@ -235,14 +235,15 @@ public class DashRendererBuilder implements RendererBuilder,
...
@@ -235,14 +235,15 @@ public class DashRendererBuilder implements RendererBuilder,
DataSource
videoDataSource
=
new
DefaultUriDataSource
(
context
,
bandwidthMeter
,
userAgent
);
DataSource
videoDataSource
=
new
DefaultUriDataSource
(
context
,
bandwidthMeter
,
userAgent
);
ChunkSource
videoChunkSource
=
new
DashChunkSource
(
manifestFetcher
,
ChunkSource
videoChunkSource
=
new
DashChunkSource
(
manifestFetcher
,
videoAdaptationSetIndex
,
videoRepresentationIndices
,
videoDataSource
,
videoAdaptationSetIndex
,
videoRepresentationIndices
,
videoDataSource
,
new
AdaptiveEvaluator
(
bandwidthMeter
),
LIVE_EDGE_LATENCY_MS
,
elapsedRealtimeOffset
);
new
AdaptiveEvaluator
(
bandwidthMeter
),
LIVE_EDGE_LATENCY_MS
,
elapsedRealtimeOffset
,
mainHandler
,
player
);
ChunkSampleSource
videoSampleSource
=
new
ChunkSampleSource
(
videoChunkSource
,
loadControl
,
ChunkSampleSource
videoSampleSource
=
new
ChunkSampleSource
(
videoChunkSource
,
loadControl
,
VIDEO_BUFFER_SEGMENTS
*
BUFFER_SEGMENT_SIZE
,
true
,
mainHandler
,
player
,
VIDEO_BUFFER_SEGMENTS
*
BUFFER_SEGMENT_SIZE
,
true
,
mainHandler
,
player
,
DemoPlayer
.
TYPE_VIDEO
);
DemoPlayer
.
TYPE_VIDEO
);
videoRenderer
=
new
MediaCodecVideoTrackRenderer
(
videoSampleSource
,
drmSessionManager
,
true
,
videoRenderer
=
new
MediaCodecVideoTrackRenderer
(
videoSampleSource
,
drmSessionManager
,
true
,
MediaCodec
.
VIDEO_SCALING_MODE_SCALE_TO_FIT
,
5000
,
null
,
mainHandler
,
player
,
50
);
MediaCodec
.
VIDEO_SCALING_MODE_SCALE_TO_FIT
,
5000
,
null
,
mainHandler
,
player
,
50
);
debugRenderer
=
debugTextView
!=
null
debugRenderer
=
debugTextView
!=
null
?
new
DebugTrackRenderer
(
debugTextView
,
player
,
videoRenderer
)
:
null
;
?
new
DebugTrackRenderer
(
debugTextView
,
player
,
videoRenderer
,
bandwidthMeter
)
:
null
;
}
}
// Build the audio chunk sources.
// Build the audio chunk sources.
...
@@ -259,7 +260,7 @@ public class DashRendererBuilder implements RendererBuilder,
...
@@ -259,7 +260,7 @@ public class DashRendererBuilder implements RendererBuilder,
format
.
audioSamplingRate
+
"Hz)"
);
format
.
audioSamplingRate
+
"Hz)"
);
audioChunkSourceList
.
add
(
new
DashChunkSource
(
manifestFetcher
,
audioAdaptationSetIndex
,
audioChunkSourceList
.
add
(
new
DashChunkSource
(
manifestFetcher
,
audioAdaptationSetIndex
,
new
int
[]
{
i
},
audioDataSource
,
audioEvaluator
,
LIVE_EDGE_LATENCY_MS
,
new
int
[]
{
i
},
audioDataSource
,
audioEvaluator
,
LIVE_EDGE_LATENCY_MS
,
elapsedRealtimeOffset
));
elapsedRealtimeOffset
,
mainHandler
,
player
));
codecs
.
add
(
format
.
codecs
);
codecs
.
add
(
format
.
codecs
);
}
}
...
@@ -316,7 +317,8 @@ public class DashRendererBuilder implements RendererBuilder,
...
@@ -316,7 +317,8 @@ public class DashRendererBuilder implements RendererBuilder,
Representation
representation
=
representations
.
get
(
j
);
Representation
representation
=
representations
.
get
(
j
);
textTrackNameList
.
add
(
representation
.
format
.
id
);
textTrackNameList
.
add
(
representation
.
format
.
id
);
textChunkSourceList
.
add
(
new
DashChunkSource
(
manifestFetcher
,
i
,
new
int
[]
{
j
},
textChunkSourceList
.
add
(
new
DashChunkSource
(
manifestFetcher
,
i
,
new
int
[]
{
j
},
textDataSource
,
textEvaluator
,
LIVE_EDGE_LATENCY_MS
,
elapsedRealtimeOffset
));
textDataSource
,
textEvaluator
,
LIVE_EDGE_LATENCY_MS
,
elapsedRealtimeOffset
,
mainHandler
,
player
));
}
}
}
}
}
}
...
...
demo/src/main/java/com/google/android/exoplayer/demo/player/DebugTrackRenderer.java
View file @
709fc773
...
@@ -19,6 +19,7 @@ import com.google.android.exoplayer.ExoPlaybackException;
...
@@ -19,6 +19,7 @@ import com.google.android.exoplayer.ExoPlaybackException;
import
com.google.android.exoplayer.MediaCodecTrackRenderer
;
import
com.google.android.exoplayer.MediaCodecTrackRenderer
;
import
com.google.android.exoplayer.TrackRenderer
;
import
com.google.android.exoplayer.TrackRenderer
;
import
com.google.android.exoplayer.chunk.Format
;
import
com.google.android.exoplayer.chunk.Format
;
import
com.google.android.exoplayer.upstream.BandwidthMeter
;
import
android.widget.TextView
;
import
android.widget.TextView
;
...
@@ -31,15 +32,22 @@ import android.widget.TextView;
...
@@ -31,15 +32,22 @@ import android.widget.TextView;
private
final
TextView
textView
;
private
final
TextView
textView
;
private
final
DemoPlayer
player
;
private
final
DemoPlayer
player
;
private
final
MediaCodecTrackRenderer
renderer
;
private
final
MediaCodecTrackRenderer
renderer
;
private
final
BandwidthMeter
bandwidthMeter
;
private
volatile
boolean
pendingFailure
;
private
volatile
boolean
pendingFailure
;
private
volatile
long
currentPositionUs
;
private
volatile
long
currentPositionUs
;
public
DebugTrackRenderer
(
TextView
textView
,
DemoPlayer
player
,
public
DebugTrackRenderer
(
TextView
textView
,
DemoPlayer
player
,
MediaCodecTrackRenderer
renderer
)
{
MediaCodecTrackRenderer
renderer
)
{
this
(
textView
,
player
,
renderer
,
null
);
}
public
DebugTrackRenderer
(
TextView
textView
,
DemoPlayer
player
,
MediaCodecTrackRenderer
renderer
,
BandwidthMeter
bandwidthMeter
)
{
this
.
textView
=
textView
;
this
.
textView
=
textView
;
this
.
player
=
player
;
this
.
player
=
player
;
this
.
renderer
=
renderer
;
this
.
renderer
=
renderer
;
this
.
bandwidthMeter
=
bandwidthMeter
;
}
}
public
void
injectFailure
()
{
public
void
injectFailure
()
{
...
@@ -77,7 +85,12 @@ import android.widget.TextView;
...
@@ -77,7 +85,12 @@ import android.widget.TextView;
}
}
private
String
getRenderString
()
{
private
String
getRenderString
()
{
return
getQualityString
()
+
" "
+
renderer
.
codecCounters
.
getDebugString
();
return
getTimeString
()
+
" "
+
getQualityString
()
+
" "
+
getBandwidthString
()
+
" "
+
renderer
.
codecCounters
.
getDebugString
();
}
private
String
getTimeString
()
{
return
"ms("
+
(
currentPositionUs
/
1000
)
+
")"
;
}
}
private
String
getQualityString
()
{
private
String
getQualityString
()
{
...
@@ -86,6 +99,15 @@ import android.widget.TextView;
...
@@ -86,6 +99,15 @@ import android.widget.TextView;
:
"id:"
+
format
.
id
+
" br:"
+
format
.
bitrate
+
" h:"
+
format
.
height
;
:
"id:"
+
format
.
id
+
" br:"
+
format
.
bitrate
+
" h:"
+
format
.
height
;
}
}
private
String
getBandwidthString
()
{
if
(
bandwidthMeter
==
null
||
bandwidthMeter
.
getBitrateEstimate
()
==
BandwidthMeter
.
NO_ESTIMATE
)
{
return
"bw:?"
;
}
else
{
return
"bw:"
+
(
bandwidthMeter
.
getBitrateEstimate
()
/
1000
);
}
}
@Override
@Override
protected
long
getCurrentPositionUs
()
{
protected
long
getCurrentPositionUs
()
{
return
currentPositionUs
;
return
currentPositionUs
;
...
...
demo/src/main/java/com/google/android/exoplayer/demo/player/DemoPlayer.java
View file @
709fc773
...
@@ -21,11 +21,13 @@ import com.google.android.exoplayer.ExoPlayer;
...
@@ -21,11 +21,13 @@ import com.google.android.exoplayer.ExoPlayer;
import
com.google.android.exoplayer.MediaCodecAudioTrackRenderer
;
import
com.google.android.exoplayer.MediaCodecAudioTrackRenderer
;
import
com.google.android.exoplayer.MediaCodecTrackRenderer.DecoderInitializationException
;
import
com.google.android.exoplayer.MediaCodecTrackRenderer.DecoderInitializationException
;
import
com.google.android.exoplayer.MediaCodecVideoTrackRenderer
;
import
com.google.android.exoplayer.MediaCodecVideoTrackRenderer
;
import
com.google.android.exoplayer.TimeRange
;
import
com.google.android.exoplayer.TrackRenderer
;
import
com.google.android.exoplayer.TrackRenderer
;
import
com.google.android.exoplayer.audio.AudioTrack
;
import
com.google.android.exoplayer.audio.AudioTrack
;
import
com.google.android.exoplayer.chunk.ChunkSampleSource
;
import
com.google.android.exoplayer.chunk.ChunkSampleSource
;
import
com.google.android.exoplayer.chunk.Format
;
import
com.google.android.exoplayer.chunk.Format
;
import
com.google.android.exoplayer.chunk.MultiTrackChunkSource
;
import
com.google.android.exoplayer.chunk.MultiTrackChunkSource
;
import
com.google.android.exoplayer.dash.DashChunkSource
;
import
com.google.android.exoplayer.drm.StreamingDrmSessionManager
;
import
com.google.android.exoplayer.drm.StreamingDrmSessionManager
;
import
com.google.android.exoplayer.hls.HlsSampleSource
;
import
com.google.android.exoplayer.hls.HlsSampleSource
;
import
com.google.android.exoplayer.metadata.MetadataTrackRenderer
;
import
com.google.android.exoplayer.metadata.MetadataTrackRenderer
;
...
@@ -50,7 +52,7 @@ import java.util.concurrent.CopyOnWriteArrayList;
...
@@ -50,7 +52,7 @@ import java.util.concurrent.CopyOnWriteArrayList;
public
class
DemoPlayer
implements
ExoPlayer
.
Listener
,
ChunkSampleSource
.
EventListener
,
public
class
DemoPlayer
implements
ExoPlayer
.
Listener
,
ChunkSampleSource
.
EventListener
,
HlsSampleSource
.
EventListener
,
DefaultBandwidthMeter
.
EventListener
,
HlsSampleSource
.
EventListener
,
DefaultBandwidthMeter
.
EventListener
,
MediaCodecVideoTrackRenderer
.
EventListener
,
MediaCodecAudioTrackRenderer
.
EventListener
,
MediaCodecVideoTrackRenderer
.
EventListener
,
MediaCodecAudioTrackRenderer
.
EventListener
,
StreamingDrmSessionManager
.
EventListener
,
TextRenderer
{
StreamingDrmSessionManager
.
EventListener
,
DashChunkSource
.
EventListener
,
TextRenderer
{
/**
/**
* Builds renderers for the player.
* Builds renderers for the player.
...
@@ -132,6 +134,7 @@ public class DemoPlayer implements ExoPlayer.Listener, ChunkSampleSource.EventLi
...
@@ -132,6 +134,7 @@ public class DemoPlayer implements ExoPlayer.Listener, ChunkSampleSource.EventLi
int
mediaStartTimeMs
,
int
mediaEndTimeMs
,
long
elapsedRealtimeMs
,
long
loadDurationMs
);
int
mediaStartTimeMs
,
int
mediaEndTimeMs
,
long
elapsedRealtimeMs
,
long
loadDurationMs
);
void
onDecoderInitialized
(
String
decoderName
,
long
elapsedRealtimeMs
,
void
onDecoderInitialized
(
String
decoderName
,
long
elapsedRealtimeMs
,
long
initializationDurationMs
);
long
initializationDurationMs
);
void
onSeekRangeChanged
(
TimeRange
seekRange
);
}
}
/**
/**
...
@@ -510,6 +513,13 @@ public class DemoPlayer implements ExoPlayer.Listener, ChunkSampleSource.EventLi
...
@@ -510,6 +513,13 @@ public class DemoPlayer implements ExoPlayer.Listener, ChunkSampleSource.EventLi
processText
(
text
);
processText
(
text
);
}
}
@Override
public
void
onSeekRangeChanged
(
TimeRange
seekRange
)
{
if
(
infoListener
!=
null
)
{
infoListener
.
onSeekRangeChanged
(
seekRange
);
}
}
/* package */
MetadataTrackRenderer
.
MetadataRenderer
<
Map
<
String
,
Object
>>
/* package */
MetadataTrackRenderer
.
MetadataRenderer
<
Map
<
String
,
Object
>>
getId3MetadataRenderer
()
{
getId3MetadataRenderer
()
{
return
new
MetadataTrackRenderer
.
MetadataRenderer
<
Map
<
String
,
Object
>>()
{
return
new
MetadataTrackRenderer
.
MetadataRenderer
<
Map
<
String
,
Object
>>()
{
...
...
demo/src/main/java/com/google/android/exoplayer/demo/player/ExtractorRendererBuilder.java
View file @
709fc773
...
@@ -23,6 +23,7 @@ import com.google.android.exoplayer.demo.player.DemoPlayer.RendererBuilderCallba
...
@@ -23,6 +23,7 @@ import com.google.android.exoplayer.demo.player.DemoPlayer.RendererBuilderCallba
import
com.google.android.exoplayer.extractor.Extractor
;
import
com.google.android.exoplayer.extractor.Extractor
;
import
com.google.android.exoplayer.extractor.ExtractorSampleSource
;
import
com.google.android.exoplayer.extractor.ExtractorSampleSource
;
import
com.google.android.exoplayer.upstream.DataSource
;
import
com.google.android.exoplayer.upstream.DataSource
;
import
com.google.android.exoplayer.upstream.DefaultBandwidthMeter
;
import
com.google.android.exoplayer.upstream.DefaultUriDataSource
;
import
com.google.android.exoplayer.upstream.DefaultUriDataSource
;
import
android.content.Context
;
import
android.content.Context
;
...
@@ -55,7 +56,9 @@ public class ExtractorRendererBuilder implements RendererBuilder {
...
@@ -55,7 +56,9 @@ public class ExtractorRendererBuilder implements RendererBuilder {
@Override
@Override
public
void
buildRenderers
(
DemoPlayer
player
,
RendererBuilderCallback
callback
)
{
public
void
buildRenderers
(
DemoPlayer
player
,
RendererBuilderCallback
callback
)
{
// Build the video and audio renderers.
// Build the video and audio renderers.
DataSource
dataSource
=
new
DefaultUriDataSource
(
context
,
userAgent
);
DefaultBandwidthMeter
bandwidthMeter
=
new
DefaultBandwidthMeter
(
player
.
getMainHandler
(),
null
);
DataSource
dataSource
=
new
DefaultUriDataSource
(
context
,
bandwidthMeter
,
userAgent
);
ExtractorSampleSource
sampleSource
=
new
ExtractorSampleSource
(
uri
,
dataSource
,
extractor
,
2
,
ExtractorSampleSource
sampleSource
=
new
ExtractorSampleSource
(
uri
,
dataSource
,
extractor
,
2
,
BUFFER_SIZE
);
BUFFER_SIZE
);
MediaCodecVideoTrackRenderer
videoRenderer
=
new
MediaCodecVideoTrackRenderer
(
sampleSource
,
MediaCodecVideoTrackRenderer
videoRenderer
=
new
MediaCodecVideoTrackRenderer
(
sampleSource
,
...
@@ -66,7 +69,7 @@ public class ExtractorRendererBuilder implements RendererBuilder {
...
@@ -66,7 +69,7 @@ public class ExtractorRendererBuilder implements RendererBuilder {
// Build the debug renderer.
// Build the debug renderer.
TrackRenderer
debugRenderer
=
debugTextView
!=
null
TrackRenderer
debugRenderer
=
debugTextView
!=
null
?
new
DebugTrackRenderer
(
debugTextView
,
player
,
videoRenderer
)
:
null
;
?
new
DebugTrackRenderer
(
debugTextView
,
player
,
videoRenderer
,
bandwidthMeter
)
:
null
;
// Invoke the callback.
// Invoke the callback.
TrackRenderer
[]
renderers
=
new
TrackRenderer
[
DemoPlayer
.
RENDERER_COUNT
];
TrackRenderer
[]
renderers
=
new
TrackRenderer
[
DemoPlayer
.
RENDERER_COUNT
];
...
...
demo/src/main/java/com/google/android/exoplayer/demo/player/HlsRendererBuilder.java
View file @
709fc773
...
@@ -121,7 +121,7 @@ public class HlsRendererBuilder implements RendererBuilder, ManifestCallback<Hls
...
@@ -121,7 +121,7 @@ public class HlsRendererBuilder implements RendererBuilder, ManifestCallback<Hls
// Build the debug renderer.
// Build the debug renderer.
TrackRenderer
debugRenderer
=
debugTextView
!=
null
TrackRenderer
debugRenderer
=
debugTextView
!=
null
?
new
DebugTrackRenderer
(
debugTextView
,
player
,
videoRenderer
)
:
null
;
?
new
DebugTrackRenderer
(
debugTextView
,
player
,
videoRenderer
,
bandwidthMeter
)
:
null
;
TrackRenderer
[]
renderers
=
new
TrackRenderer
[
DemoPlayer
.
RENDERER_COUNT
];
TrackRenderer
[]
renderers
=
new
TrackRenderer
[
DemoPlayer
.
RENDERER_COUNT
];
renderers
[
DemoPlayer
.
TYPE_VIDEO
]
=
videoRenderer
;
renderers
[
DemoPlayer
.
TYPE_VIDEO
]
=
videoRenderer
;
...
...
demo/src/main/java/com/google/android/exoplayer/demo/player/SmoothStreamingRendererBuilder.java
View file @
709fc773
...
@@ -174,7 +174,7 @@ public class SmoothStreamingRendererBuilder implements RendererBuilder,
...
@@ -174,7 +174,7 @@ public class SmoothStreamingRendererBuilder implements RendererBuilder,
videoRenderer
=
new
MediaCodecVideoTrackRenderer
(
videoSampleSource
,
drmSessionManager
,
true
,
videoRenderer
=
new
MediaCodecVideoTrackRenderer
(
videoSampleSource
,
drmSessionManager
,
true
,
MediaCodec
.
VIDEO_SCALING_MODE_SCALE_TO_FIT
,
5000
,
null
,
mainHandler
,
player
,
50
);
MediaCodec
.
VIDEO_SCALING_MODE_SCALE_TO_FIT
,
5000
,
null
,
mainHandler
,
player
,
50
);
debugRenderer
=
debugTextView
!=
null
debugRenderer
=
debugTextView
!=
null
?
new
DebugTrackRenderer
(
debugTextView
,
player
,
videoRenderer
)
:
null
;
?
new
DebugTrackRenderer
(
debugTextView
,
player
,
videoRenderer
,
bandwidthMeter
)
:
null
;
}
}
// Build the audio renderer.
// Build the audio renderer.
...
...
library/src/main/java/com/google/android/exoplayer/TimeRange.java
0 → 100644
View file @
709fc773
/*
* Copyright (C) 2014 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
com
.
google
.
android
.
exoplayer
;
/**
* A container to store a start and end time in microseconds.
*/
public
final
class
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.
*/
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.
* @param startTimeUs The beginning of the TimeRange.
* @param endTimeUs The end of the TimeRange.
*/
public
TimeRange
(
int
type
,
long
startTimeUs
,
long
endTimeUs
)
{
this
.
type
=
type
;
this
.
startTimeUs
=
startTimeUs
;
this
.
endTimeUs
=
endTimeUs
;
}
/**
* Returns the start and end times (in milliseconds) of the TimeRange in the provided array,
* or creates a new one.
*
* @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.
*/
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,
* or creates a new one.
*
* @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.
*/
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
hashCode
=
0
;
hashCode
|=
type
<<
30
;
hashCode
|=
(((
startTimeUs
+
endTimeUs
)
/
1000
)
&
0x3FFFFFFF
);
return
hashCode
;
}
@Override
public
boolean
equals
(
Object
other
)
{
if
(
other
==
this
)
{
return
true
;
}
if
(
other
instanceof
TimeRange
)
{
TimeRange
otherTimeRange
=
(
TimeRange
)
other
;
return
(
otherTimeRange
.
type
==
type
)
&&
(
otherTimeRange
.
startTimeUs
==
startTimeUs
)
&&
(
otherTimeRange
.
endTimeUs
==
endTimeUs
);
}
else
{
return
false
;
}
}
}
library/src/main/java/com/google/android/exoplayer/dash/DashChunkSource.java
View file @
709fc773
...
@@ -17,6 +17,7 @@ package com.google.android.exoplayer.dash;
...
@@ -17,6 +17,7 @@ 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.TrackInfo
;
import
com.google.android.exoplayer.TrackInfo
;
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
;
...
@@ -50,6 +51,8 @@ import com.google.android.exoplayer.util.ManifestFetcher;
...
@@ -50,6 +51,8 @@ import com.google.android.exoplayer.util.ManifestFetcher;
import
com.google.android.exoplayer.util.MimeTypes
;
import
com.google.android.exoplayer.util.MimeTypes
;
import
com.google.android.exoplayer.util.SystemClock
;
import
com.google.android.exoplayer.util.SystemClock
;
import
android.os.Handler
;
import
java.io.IOException
;
import
java.io.IOException
;
import
java.util.Arrays
;
import
java.util.Arrays
;
import
java.util.Collections
;
import
java.util.Collections
;
...
@@ -64,6 +67,20 @@ import java.util.List;
...
@@ -64,6 +67,20 @@ import java.util.List;
public
class
DashChunkSource
implements
ChunkSource
{
public
class
DashChunkSource
implements
ChunkSource
{
/**
/**
* Interface definition for a callback to be notified of {@link DashChunkSource} events.
*/
public
interface
EventListener
{
/**
* Invoked when the available seek range of the stream has changed.
*
* @param seekRange The range which specifies available content that can be seeked to.
*/
public
void
onSeekRangeChanged
(
TimeRange
seekRange
);
}
/**
* Thrown when an AdaptationSet is missing from the MPD.
* Thrown when an AdaptationSet is missing from the MPD.
*/
*/
public
static
class
NoAdaptationSetException
extends
IOException
{
public
static
class
NoAdaptationSetException
extends
IOException
{
...
@@ -79,6 +96,9 @@ public class DashChunkSource implements ChunkSource {
...
@@ -79,6 +96,9 @@ public class DashChunkSource implements ChunkSource {
*/
*/
public
static
final
int
USE_ALL_TRACKS
=
-
1
;
public
static
final
int
USE_ALL_TRACKS
=
-
1
;
private
final
Handler
eventHandler
;
private
final
EventListener
eventListener
;
private
final
TrackInfo
trackInfo
;
private
final
TrackInfo
trackInfo
;
private
final
DataSource
dataSource
;
private
final
DataSource
dataSource
;
private
final
FormatEvaluator
evaluator
;
private
final
FormatEvaluator
evaluator
;
...
@@ -99,6 +119,10 @@ public class DashChunkSource implements ChunkSource {
...
@@ -99,6 +119,10 @@ public class DashChunkSource implements ChunkSource {
private
DrmInitData
drmInitData
;
private
DrmInitData
drmInitData
;
private
MediaPresentationDescription
currentManifest
;
private
MediaPresentationDescription
currentManifest
;
private
TimeRange
seekRange
;
private
long
[]
seekRangeValues
;
private
int
firstAvailableSegmentNum
;
private
int
lastAvailableSegmentNum
;
private
boolean
finishedCurrentManifest
;
private
boolean
finishedCurrentManifest
;
private
boolean
lastChunkWasInitialization
;
private
boolean
lastChunkWasInitialization
;
...
@@ -142,7 +166,7 @@ public class DashChunkSource implements ChunkSource {
...
@@ -142,7 +166,7 @@ public class DashChunkSource implements ChunkSource {
public
DashChunkSource
(
MediaPresentationDescription
manifest
,
int
adaptationSetIndex
,
public
DashChunkSource
(
MediaPresentationDescription
manifest
,
int
adaptationSetIndex
,
int
[]
representationIndices
,
DataSource
dataSource
,
FormatEvaluator
formatEvaluator
)
{
int
[]
representationIndices
,
DataSource
dataSource
,
FormatEvaluator
formatEvaluator
)
{
this
(
null
,
manifest
,
adaptationSetIndex
,
representationIndices
,
dataSource
,
formatEvaluator
,
this
(
null
,
manifest
,
adaptationSetIndex
,
representationIndices
,
dataSource
,
formatEvaluator
,
new
SystemClock
(),
0
,
0
);
new
SystemClock
(),
0
,
0
,
null
,
null
);
}
}
/**
/**
...
@@ -167,19 +191,24 @@ public class DashChunkSource implements ChunkSource {
...
@@ -167,19 +191,24 @@ public class DashChunkSource implements ChunkSource {
* @param elapsedRealtimeOffsetMs If known, an estimate of the instantaneous difference between
* @param elapsedRealtimeOffsetMs If known, an estimate of the instantaneous difference between
* server-side unix time and {@link SystemClock#elapsedRealtime()} in milliseconds, specified
* server-side unix time and {@link SystemClock#elapsedRealtime()} in milliseconds, specified
* as the server's unix time minus the local elapsed time. It unknown, set to 0.
* as the server's unix time minus the local elapsed time. It unknown, set to 0.
* @param eventHandler A handler to use when delivering events to {@code EventListener}. May be
* null if delivery of events is not required.
* @param eventListener A listener of events. May be null if delivery of events is not required.
*/
*/
public
DashChunkSource
(
ManifestFetcher
<
MediaPresentationDescription
>
manifestFetcher
,
public
DashChunkSource
(
ManifestFetcher
<
MediaPresentationDescription
>
manifestFetcher
,
int
adaptationSetIndex
,
int
[]
representationIndices
,
DataSource
dataSource
,
int
adaptationSetIndex
,
int
[]
representationIndices
,
DataSource
dataSource
,
FormatEvaluator
formatEvaluator
,
long
liveEdgeLatencyMs
,
long
elapsedRealtimeOffsetMs
)
{
FormatEvaluator
formatEvaluator
,
long
liveEdgeLatencyMs
,
long
elapsedRealtimeOffsetMs
,
Handler
eventHandler
,
EventListener
eventListener
)
{
this
(
manifestFetcher
,
manifestFetcher
.
getManifest
(),
adaptationSetIndex
,
representationIndices
,
this
(
manifestFetcher
,
manifestFetcher
.
getManifest
(),
adaptationSetIndex
,
representationIndices
,
dataSource
,
formatEvaluator
,
new
SystemClock
(),
liveEdgeLatencyMs
*
1000
,
dataSource
,
formatEvaluator
,
new
SystemClock
(),
liveEdgeLatencyMs
*
1000
,
elapsedRealtimeOffsetMs
*
1000
);
elapsedRealtimeOffsetMs
*
1000
,
eventHandler
,
eventListener
);
}
}
/* package */
DashChunkSource
(
ManifestFetcher
<
MediaPresentationDescription
>
manifestFetcher
,
/* package */
DashChunkSource
(
ManifestFetcher
<
MediaPresentationDescription
>
manifestFetcher
,
MediaPresentationDescription
initialManifest
,
int
adaptationSetIndex
,
MediaPresentationDescription
initialManifest
,
int
adaptationSetIndex
,
int
[]
representationIndices
,
DataSource
dataSource
,
FormatEvaluator
formatEvaluator
,
int
[]
representationIndices
,
DataSource
dataSource
,
FormatEvaluator
formatEvaluator
,
Clock
systemClock
,
long
liveEdgeLatencyUs
,
long
elapsedRealtimeOffsetUs
)
{
Clock
systemClock
,
long
liveEdgeLatencyUs
,
long
elapsedRealtimeOffsetUs
,
Handler
eventHandler
,
EventListener
eventListener
)
{
this
.
manifestFetcher
=
manifestFetcher
;
this
.
manifestFetcher
=
manifestFetcher
;
this
.
currentManifest
=
initialManifest
;
this
.
currentManifest
=
initialManifest
;
this
.
adaptationSetIndex
=
adaptationSetIndex
;
this
.
adaptationSetIndex
=
adaptationSetIndex
;
...
@@ -189,8 +218,11 @@ public class DashChunkSource implements ChunkSource {
...
@@ -189,8 +218,11 @@ public class DashChunkSource implements ChunkSource {
this
.
systemClock
=
systemClock
;
this
.
systemClock
=
systemClock
;
this
.
liveEdgeLatencyUs
=
liveEdgeLatencyUs
;
this
.
liveEdgeLatencyUs
=
liveEdgeLatencyUs
;
this
.
elapsedRealtimeOffsetUs
=
elapsedRealtimeOffsetUs
;
this
.
elapsedRealtimeOffsetUs
=
elapsedRealtimeOffsetUs
;
this
.
eventHandler
=
eventHandler
;
this
.
eventListener
=
eventListener
;
this
.
evaluation
=
new
Evaluation
();
this
.
evaluation
=
new
Evaluation
();
this
.
headerBuilder
=
new
StringBuilder
();
this
.
headerBuilder
=
new
StringBuilder
();
this
.
seekRangeValues
=
new
long
[
2
];
drmInitData
=
getDrmInitData
(
currentManifest
,
adaptationSetIndex
);
drmInitData
=
getDrmInitData
(
currentManifest
,
adaptationSetIndex
);
Representation
[]
representations
=
getFilteredRepresentations
(
currentManifest
,
Representation
[]
representations
=
getFilteredRepresentations
(
currentManifest
,
...
@@ -229,12 +261,27 @@ public class DashChunkSource implements ChunkSource {
...
@@ -229,12 +261,27 @@ public class DashChunkSource implements ChunkSource {
return
trackInfo
;
return
trackInfo
;
}
}
// VisibleForTesting
/* package */
TimeRange
getSeekRange
()
{
return
seekRange
;
}
@Override
@Override
public
void
enable
()
{
public
void
enable
()
{
evaluator
.
enable
();
evaluator
.
enable
();
if
(
manifestFetcher
!=
null
)
{
if
(
manifestFetcher
!=
null
)
{
manifestFetcher
.
enable
();
manifestFetcher
.
enable
();
}
}
DashSegmentIndex
segmentIndex
=
representationHolders
.
get
(
formats
[
0
].
id
).
representation
.
getIndex
();
if
(
segmentIndex
==
null
)
{
seekRange
=
new
TimeRange
(
TimeRange
.
TYPE_SNAPSHOT
,
0
,
currentManifest
.
duration
*
1000
);
notifySeekRangeChanged
(
seekRange
);
}
else
{
long
nowUs
=
getNowUs
();
updateAvailableSegmentBounds
(
segmentIndex
,
nowUs
);
updateSeekRange
(
segmentIndex
,
nowUs
);
}
}
}
@Override
@Override
...
@@ -243,6 +290,7 @@ public class DashChunkSource implements ChunkSource {
...
@@ -243,6 +290,7 @@ public class DashChunkSource implements ChunkSource {
if
(
manifestFetcher
!=
null
)
{
if
(
manifestFetcher
!=
null
)
{
manifestFetcher
.
disable
();
manifestFetcher
.
disable
();
}
}
seekRange
=
null
;
}
}
@Override
@Override
...
@@ -268,6 +316,10 @@ public class DashChunkSource implements ChunkSource {
...
@@ -268,6 +316,10 @@ public class DashChunkSource implements ChunkSource {
}
}
currentManifest
=
newManifest
;
currentManifest
=
newManifest
;
finishedCurrentManifest
=
false
;
finishedCurrentManifest
=
false
;
long
nowUs
=
getNowUs
();
updateAvailableSegmentBounds
(
newRepresentations
[
0
].
getIndex
(),
nowUs
);
updateSeekRange
(
newRepresentations
[
0
].
getIndex
(),
nowUs
);
}
}
// 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
...
@@ -334,36 +386,21 @@ public class DashChunkSource implements ChunkSource {
...
@@ -334,36 +386,21 @@ public class DashChunkSource implements ChunkSource {
return
;
return
;
}
}
long
nowUs
;
if
(
elapsedRealtimeOffsetUs
!=
0
)
{
nowUs
=
(
systemClock
.
elapsedRealtime
()
*
1000
)
+
elapsedRealtimeOffsetUs
;
}
else
{
nowUs
=
System
.
currentTimeMillis
()
*
1000
;
}
int
firstAvailableSegmentNum
=
segmentIndex
.
getFirstSegmentNum
();
int
lastAvailableSegmentNum
=
segmentIndex
.
getLastSegmentNum
();
boolean
indexUnbounded
=
lastAvailableSegmentNum
==
DashSegmentIndex
.
INDEX_UNBOUNDED
;
if
(
indexUnbounded
)
{
// The index is itself unbounded. We need to use the current time to calculate the range of
// available segments.
long
liveEdgeTimestampUs
=
nowUs
-
currentManifest
.
availabilityStartTime
*
1000
;
if
(
currentManifest
.
timeShiftBufferDepth
!=
-
1
)
{
long
bufferDepthUs
=
currentManifest
.
timeShiftBufferDepth
*
1000
;
firstAvailableSegmentNum
=
Math
.
max
(
firstAvailableSegmentNum
,
segmentIndex
.
getSegmentNum
(
liveEdgeTimestampUs
-
bufferDepthUs
));
}
// getSegmentNum(liveEdgeTimestampUs) will not be completed yet, so subtract one to get the
// index of the last completed segment.
lastAvailableSegmentNum
=
segmentIndex
.
getSegmentNum
(
liveEdgeTimestampUs
)
-
1
;
}
int
segmentNum
;
int
segmentNum
;
boolean
indexUnbounded
=
segmentIndex
.
getLastSegmentNum
()
==
DashSegmentIndex
.
INDEX_UNBOUNDED
;
if
(
queue
.
isEmpty
())
{
if
(
queue
.
isEmpty
())
{
if
(
currentManifest
.
dynamic
)
{
if
(
currentManifest
.
dynamic
)
{
seekPositionUs
=
getLiveSeekPosition
(
nowUs
,
indexUnbounded
,
segmentIndex
.
isExplicit
());
seekRangeValues
=
seekRange
.
getCurrentBoundsUs
(
seekRangeValues
);
seekPositionUs
=
Math
.
max
(
seekPositionUs
,
seekRangeValues
[
0
]);
seekPositionUs
=
Math
.
min
(
seekPositionUs
,
seekRangeValues
[
1
]);
}
}
segmentNum
=
segmentIndex
.
getSegmentNum
(
seekPositionUs
);
segmentNum
=
segmentIndex
.
getSegmentNum
(
seekPositionUs
);
// if the index is unbounded then the result of getSegmentNum isn't clamped to ensure that
// it doesn't exceed the last available segment. Clamp it here.
if
(
indexUnbounded
)
{
segmentNum
=
Math
.
min
(
segmentNum
,
lastAvailableSegmentNum
);
}
}
else
{
}
else
{
MediaChunk
previous
=
queue
.
get
(
out
.
queueSize
-
1
);
MediaChunk
previous
=
queue
.
get
(
out
.
queueSize
-
1
);
segmentNum
=
previous
.
isLastChunk
?
-
1
segmentNum
=
previous
.
isLastChunk
?
-
1
...
@@ -432,6 +469,59 @@ public class DashChunkSource implements ChunkSource {
...
@@ -432,6 +469,59 @@ public class DashChunkSource implements ChunkSource {
// Do nothing.
// Do nothing.
}
}
private
void
updateAvailableSegmentBounds
(
DashSegmentIndex
segmentIndex
,
long
nowUs
)
{
int
indexFirstAvailableSegmentNum
=
segmentIndex
.
getFirstSegmentNum
();
int
indexLastAvailableSegmentNum
=
segmentIndex
.
getLastSegmentNum
();
if
(
indexLastAvailableSegmentNum
==
DashSegmentIndex
.
INDEX_UNBOUNDED
)
{
// The index is itself unbounded. We need to use the current time to calculate the range of
// available segments.
long
liveEdgeTimestampUs
=
nowUs
-
currentManifest
.
availabilityStartTime
*
1000
;
if
(
currentManifest
.
timeShiftBufferDepth
!=
-
1
)
{
long
bufferDepthUs
=
currentManifest
.
timeShiftBufferDepth
*
1000
;
indexFirstAvailableSegmentNum
=
Math
.
max
(
indexFirstAvailableSegmentNum
,
segmentIndex
.
getSegmentNum
(
liveEdgeTimestampUs
-
bufferDepthUs
));
}
// getSegmentNum(liveEdgeTimestampUs) will not be completed yet, so subtract one to get the
// index of the last completed segment.
indexLastAvailableSegmentNum
=
segmentIndex
.
getSegmentNum
(
liveEdgeTimestampUs
)
-
1
;
}
firstAvailableSegmentNum
=
indexFirstAvailableSegmentNum
;
lastAvailableSegmentNum
=
indexLastAvailableSegmentNum
;
}
private
void
updateSeekRange
(
DashSegmentIndex
segmentIndex
,
long
nowUs
)
{
long
earliestSeekPosition
=
segmentIndex
.
getTimeUs
(
firstAvailableSegmentNum
);
long
latestSeekPosition
=
segmentIndex
.
getTimeUs
(
lastAvailableSegmentNum
)
+
segmentIndex
.
getDurationUs
(
lastAvailableSegmentNum
);
if
(
currentManifest
.
dynamic
)
{
long
liveEdgeTimestampUs
;
if
(
segmentIndex
.
getLastSegmentNum
()
==
DashSegmentIndex
.
INDEX_UNBOUNDED
)
{
liveEdgeTimestampUs
=
nowUs
-
currentManifest
.
availabilityStartTime
*
1000
;
}
else
{
liveEdgeTimestampUs
=
segmentIndex
.
getTimeUs
(
segmentIndex
.
getLastSegmentNum
())
+
segmentIndex
.
getDurationUs
(
segmentIndex
.
getLastSegmentNum
());
if
(!
segmentIndex
.
isExplicit
())
{
// 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.
liveEdgeTimestampUs
=
Math
.
min
(
liveEdgeTimestampUs
,
nowUs
-
currentManifest
.
availabilityStartTime
*
1000
);
}
}
// it's possible that the live edge latency actually puts our latest position before
// the earliest position in the case of a DVR-like stream that's just starting up, so
// in that case just return the earliest position instead
latestSeekPosition
=
Math
.
max
(
earliestSeekPosition
,
liveEdgeTimestampUs
-
liveEdgeLatencyUs
);
}
TimeRange
newSeekRange
=
new
TimeRange
(
TimeRange
.
TYPE_SNAPSHOT
,
earliestSeekPosition
,
latestSeekPosition
);
if
(
seekRange
==
null
||
!
seekRange
.
equals
(
newSeekRange
))
{
seekRange
=
newSeekRange
;
notifySeekRangeChanged
(
seekRange
);
}
}
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
);
}
}
...
@@ -491,36 +581,12 @@ public class DashChunkSource implements ChunkSource {
...
@@ -491,36 +581,12 @@ public class DashChunkSource implements ChunkSource {
}
}
}
}
/**
private
long
getNowUs
()
{
* For live playbacks, determines the seek position that snaps playback to be
if
(
elapsedRealtimeOffsetUs
!=
0
)
{
* {@link #liveEdgeLatencyUs} behind the live edge of the current manifest
return
(
systemClock
.
elapsedRealtime
()
*
1000
)
+
elapsedRealtimeOffsetUs
;
*
* @param nowUs An estimate of the current server time, in microseconds.
* @param indexUnbounded True if the segment index for this source is unbounded. False otherwise.
* @param indexExplicit True if the segment index is explicit. False otherwise.
* @return The seek position in microseconds.
*/
private
long
getLiveSeekPosition
(
long
nowUs
,
boolean
indexUnbounded
,
boolean
indexExplicit
)
{
long
liveEdgeTimestampUs
;
if
(
indexUnbounded
)
{
liveEdgeTimestampUs
=
nowUs
-
currentManifest
.
availabilityStartTime
*
1000
;
}
else
{
}
else
{
liveEdgeTimestampUs
=
Long
.
MIN_VALUE
;
return
System
.
currentTimeMillis
()
*
1000
;
for
(
RepresentationHolder
representationHolder
:
representationHolders
.
values
())
{
DashSegmentIndex
segmentIndex
=
representationHolder
.
segmentIndex
;
int
lastSegmentNum
=
segmentIndex
.
getLastSegmentNum
();
long
indexLiveEdgeTimestampUs
=
segmentIndex
.
getTimeUs
(
lastSegmentNum
)
+
segmentIndex
.
getDurationUs
(
lastSegmentNum
);
liveEdgeTimestampUs
=
Math
.
max
(
liveEdgeTimestampUs
,
indexLiveEdgeTimestampUs
);
}
if
(!
indexExplicit
)
{
// 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.
liveEdgeTimestampUs
=
Math
.
min
(
liveEdgeTimestampUs
,
nowUs
-
currentManifest
.
availabilityStartTime
*
1000
);
}
}
}
return
liveEdgeTimestampUs
-
liveEdgeLatencyUs
;
}
}
private
static
Representation
[]
getFilteredRepresentations
(
MediaPresentationDescription
manifest
,
private
static
Representation
[]
getFilteredRepresentations
(
MediaPresentationDescription
manifest
,
...
@@ -571,6 +637,17 @@ public class DashChunkSource implements ChunkSource {
...
@@ -571,6 +637,17 @@ public class DashChunkSource implements ChunkSource {
Collections
.
singletonList
(
period
));
Collections
.
singletonList
(
period
));
}
}
private
void
notifySeekRangeChanged
(
final
TimeRange
seekRange
)
{
if
(
eventHandler
!=
null
&&
eventListener
!=
null
)
{
eventHandler
.
post
(
new
Runnable
()
{
@Override
public
void
run
()
{
eventListener
.
onSeekRangeChanged
(
seekRange
);
}
});
}
}
private
static
class
RepresentationHolder
{
private
static
class
RepresentationHolder
{
public
final
Representation
representation
;
public
final
Representation
representation
;
...
...
library/src/main/java/com/google/android/exoplayer/extractor/ts/H264Reader.java
View file @
709fc773
...
@@ -37,11 +37,15 @@ import java.util.List;
...
@@ -37,11 +37,15 @@ import java.util.List;
private
static
final
String
TAG
=
"H264Reader"
;
private
static
final
String
TAG
=
"H264Reader"
;
private
static
final
int
NAL_UNIT_TYPE_IDR
=
5
;
private
static
final
int
FRAME_TYPE_I
=
2
;
private
static
final
int
NAL_UNIT_TYPE_SEI
=
6
;
private
static
final
int
FRAME_TYPE_ALL_I
=
7
;
private
static
final
int
NAL_UNIT_TYPE_SPS
=
7
;
private
static
final
int
NAL_UNIT_TYPE_PPS
=
8
;
private
static
final
int
NAL_UNIT_TYPE_IFR
=
1
;
// Coded slice of a non-IDR picture
private
static
final
int
NAL_UNIT_TYPE_AUD
=
9
;
private
static
final
int
NAL_UNIT_TYPE_IDR
=
5
;
// Coded slice of an IDR picture
private
static
final
int
NAL_UNIT_TYPE_SEI
=
6
;
// Supplemental enhancement information
private
static
final
int
NAL_UNIT_TYPE_SPS
=
7
;
// Sequence parameter set
private
static
final
int
NAL_UNIT_TYPE_PPS
=
8
;
// Picture parameter set
private
static
final
int
NAL_UNIT_TYPE_AUD
=
9
;
// Access unit delimiter
private
static
final
int
EXTENDED_SAR
=
0xFF
;
private
static
final
int
EXTENDED_SAR
=
0xFF
;
private
static
final
float
[]
ASPECT_RATIO_IDC_VALUES
=
new
float
[]
{
private
static
final
float
[]
ASPECT_RATIO_IDC_VALUES
=
new
float
[]
{
1
f
/* Unspecified. Assume square */
,
1
f
/* Unspecified. Assume square */
,
...
@@ -69,6 +73,7 @@ import java.util.List;
...
@@ -69,6 +73,7 @@ import java.util.List;
// State that should be reset on seek.
// State that should be reset on seek.
private
final
SeiReader
seiReader
;
private
final
SeiReader
seiReader
;
private
final
boolean
[]
prefixFlags
;
private
final
boolean
[]
prefixFlags
;
private
final
IfrParserBuffer
ifrParserBuffer
;
private
final
NalUnitTargetBuffer
sps
;
private
final
NalUnitTargetBuffer
sps
;
private
final
NalUnitTargetBuffer
pps
;
private
final
NalUnitTargetBuffer
pps
;
private
final
NalUnitTargetBuffer
sei
;
private
final
NalUnitTargetBuffer
sei
;
...
@@ -84,10 +89,11 @@ import java.util.List;
...
@@ -84,10 +89,11 @@ import java.util.List;
private
final
ParsableByteArray
seiWrapper
;
private
final
ParsableByteArray
seiWrapper
;
private
int
[]
scratchEscapePositions
;
private
int
[]
scratchEscapePositions
;
public
H264Reader
(
TrackOutput
output
,
SeiReader
seiReader
)
{
public
H264Reader
(
TrackOutput
output
,
SeiReader
seiReader
,
boolean
idrKeyframesOnly
)
{
super
(
output
);
super
(
output
);
this
.
seiReader
=
seiReader
;
this
.
seiReader
=
seiReader
;
prefixFlags
=
new
boolean
[
3
];
prefixFlags
=
new
boolean
[
3
];
ifrParserBuffer
=
(
idrKeyframesOnly
)
?
null
:
new
IfrParserBuffer
();
sps
=
new
NalUnitTargetBuffer
(
NAL_UNIT_TYPE_SPS
,
128
);
sps
=
new
NalUnitTargetBuffer
(
NAL_UNIT_TYPE_SPS
,
128
);
pps
=
new
NalUnitTargetBuffer
(
NAL_UNIT_TYPE_PPS
,
128
);
pps
=
new
NalUnitTargetBuffer
(
NAL_UNIT_TYPE_PPS
,
128
);
sei
=
new
NalUnitTargetBuffer
(
NAL_UNIT_TYPE_SEI
,
128
);
sei
=
new
NalUnitTargetBuffer
(
NAL_UNIT_TYPE_SEI
,
128
);
...
@@ -102,6 +108,9 @@ import java.util.List;
...
@@ -102,6 +108,9 @@ import java.util.List;
sps
.
reset
();
sps
.
reset
();
pps
.
reset
();
pps
.
reset
();
sei
.
reset
();
sei
.
reset
();
if
(
ifrParserBuffer
!=
null
)
{
ifrParserBuffer
.
reset
();
}
writingSample
=
false
;
writingSample
=
false
;
totalBytesWritten
=
0
;
totalBytesWritten
=
0
;
}
}
...
@@ -132,22 +141,30 @@ import java.util.List;
...
@@ -132,22 +141,30 @@ import java.util.List;
int
nalUnitType
=
H264Util
.
getNalUnitType
(
dataArray
,
nextNalUnitOffset
);
int
nalUnitType
=
H264Util
.
getNalUnitType
(
dataArray
,
nextNalUnitOffset
);
int
bytesWrittenPastNalUnit
=
limit
-
nextNalUnitOffset
;
int
bytesWrittenPastNalUnit
=
limit
-
nextNalUnitOffset
;
if
(
nalUnitType
==
NAL_UNIT_TYPE_AUD
)
{
switch
(
nalUnitType
)
{
if
(
writingSample
)
{
case
NAL_UNIT_TYPE_IDR:
if
(
isKeyframe
&&
!
hasOutputFormat
&&
sps
.
isCompleted
()
&&
pps
.
isCompleted
())
{
isKeyframe
=
true
;
parseMediaFormat
(
sps
,
pps
);
break
;
case
NAL_UNIT_TYPE_AUD:
if
(
writingSample
)
{
if
(
ifrParserBuffer
!=
null
&&
ifrParserBuffer
.
isCompleted
())
{
int
sliceType
=
ifrParserBuffer
.
getSliceType
();
isKeyframe
|=
(
sliceType
==
FRAME_TYPE_I
||
sliceType
==
FRAME_TYPE_ALL_I
);
ifrParserBuffer
.
reset
();
}
if
(
isKeyframe
&&
!
hasOutputFormat
&&
sps
.
isCompleted
()
&&
pps
.
isCompleted
())
{
parseMediaFormat
(
sps
,
pps
);
}
int
flags
=
isKeyframe
?
C
.
SAMPLE_FLAG_SYNC
:
0
;
int
size
=
(
int
)
(
totalBytesWritten
-
samplePosition
)
-
bytesWrittenPastNalUnit
;
output
.
sampleMetadata
(
sampleTimeUs
,
flags
,
size
,
bytesWrittenPastNalUnit
,
null
);
writingSample
=
false
;
}
}
int
flags
=
isKeyframe
?
C
.
SAMPLE_FLAG_SYNC
:
0
;
writingSample
=
true
;
int
size
=
(
int
)
(
totalBytesWritten
-
samplePosition
)
-
bytesWrittenPastNalUnit
;
samplePosition
=
totalBytesWritten
-
bytesWrittenPastNalUnit
;
output
.
sampleMetadata
(
sampleTimeUs
,
flags
,
size
,
bytesWrittenPastNalUnit
,
null
);
sampleTimeUs
=
pesTimeUs
;
writingSample
=
false
;
isKeyframe
=
false
;
}
break
;
writingSample
=
true
;
isKeyframe
=
false
;
sampleTimeUs
=
pesTimeUs
;
samplePosition
=
totalBytesWritten
-
bytesWrittenPastNalUnit
;
}
else
if
(
nalUnitType
==
NAL_UNIT_TYPE_IDR
)
{
isKeyframe
=
true
;
}
}
// If the length to the start of the unit is negative then we wrote too many bytes to the
// If the length to the start of the unit is negative then we wrote too many bytes to the
...
@@ -171,6 +188,9 @@ import java.util.List;
...
@@ -171,6 +188,9 @@ import java.util.List;
}
}
private
void
feedNalUnitTargetBuffersStart
(
int
nalUnitType
)
{
private
void
feedNalUnitTargetBuffersStart
(
int
nalUnitType
)
{
if
(
ifrParserBuffer
!=
null
)
{
ifrParserBuffer
.
startNalUnit
(
nalUnitType
);
}
if
(!
hasOutputFormat
)
{
if
(!
hasOutputFormat
)
{
sps
.
startNalUnit
(
nalUnitType
);
sps
.
startNalUnit
(
nalUnitType
);
pps
.
startNalUnit
(
nalUnitType
);
pps
.
startNalUnit
(
nalUnitType
);
...
@@ -179,6 +199,9 @@ import java.util.List;
...
@@ -179,6 +199,9 @@ import java.util.List;
}
}
private
void
feedNalUnitTargetBuffersData
(
byte
[]
dataArray
,
int
offset
,
int
limit
)
{
private
void
feedNalUnitTargetBuffersData
(
byte
[]
dataArray
,
int
offset
,
int
limit
)
{
if
(
ifrParserBuffer
!=
null
)
{
ifrParserBuffer
.
appendToNalUnit
(
dataArray
,
offset
,
limit
);
}
if
(!
hasOutputFormat
)
{
if
(!
hasOutputFormat
)
{
sps
.
appendToNalUnit
(
dataArray
,
offset
,
limit
);
sps
.
appendToNalUnit
(
dataArray
,
offset
,
limit
);
pps
.
appendToNalUnit
(
dataArray
,
offset
,
limit
);
pps
.
appendToNalUnit
(
dataArray
,
offset
,
limit
);
...
@@ -461,4 +484,99 @@ import java.util.List;
...
@@ -461,4 +484,99 @@ import java.util.List;
}
}
/**
* A buffer specifically for IFR units that can be used to parse the IFR's slice type.
*/
private
static
final
class
IfrParserBuffer
{
private
static
final
int
DEFAULT_BUFFER_SIZE
=
128
;
private
static
final
int
NOT_SET
=
-
1
;
private
final
ParsableBitArray
scratchSliceType
;
private
byte
[]
ifrData
;
private
int
ifrLength
;
private
boolean
isFilling
;
private
int
sliceType
;
public
IfrParserBuffer
()
{
ifrData
=
new
byte
[
DEFAULT_BUFFER_SIZE
];
scratchSliceType
=
new
ParsableBitArray
(
ifrData
);
reset
();
}
/**
* Resets the buffer, clearing any data that it holds.
*/
public
void
reset
()
{
isFilling
=
false
;
ifrLength
=
0
;
sliceType
=
NOT_SET
;
}
/**
* True if enough data was added to the buffer that the slice type was determined.
*/
public
boolean
isCompleted
()
{
return
sliceType
!=
NOT_SET
;
}
/**
* Invoked to indicate that a NAL unit has started, and if it is an IFR then the buffer will
* start.
*/
public
void
startNalUnit
(
int
nalUnitType
)
{
if
(
nalUnitType
==
NAL_UNIT_TYPE_IFR
)
{
reset
();
isFilling
=
true
;
}
}
/**
* Invoked to pass stream data. The data passed should not include 4 byte NAL unit prefixes.
*
* @param data Holds the data being passed.
* @param offset The offset of the data in {@code data}.
* @param limit The limit (exclusive) of the data in {@code data}.
*/
public
void
appendToNalUnit
(
byte
[]
data
,
int
offset
,
int
limit
)
{
if
(!
isFilling
)
{
return
;
}
int
readLength
=
limit
-
offset
;
if
(
ifrData
.
length
<
ifrLength
+
readLength
)
{
ifrData
=
Arrays
.
copyOf
(
ifrData
,
(
ifrLength
+
readLength
)
*
2
);
}
System
.
arraycopy
(
data
,
offset
,
ifrData
,
ifrLength
,
readLength
);
ifrLength
+=
readLength
;
scratchSliceType
.
reset
(
ifrData
,
ifrLength
);
// first_mb_in_slice
int
len
=
scratchSliceType
.
peekExpGolombCodedNumLength
();
if
((
len
==
-
1
)
||
(
len
>
scratchSliceType
.
bitsLeft
()))
{
// Not enough yet
return
;
}
scratchSliceType
.
skipBits
(
len
);
// slice_type
len
=
scratchSliceType
.
peekExpGolombCodedNumLength
();
if
((
len
==
-
1
)
||
(
len
>
scratchSliceType
.
bitsLeft
()))
{
// Not enough yet
return
;
}
sliceType
=
scratchSliceType
.
readUnsignedExpGolombCodedInt
();
isFilling
=
false
;
}
/**
* @return the slice type of the IFR.
*/
public
int
getSliceType
()
{
return
sliceType
;
}
}
}
}
library/src/main/java/com/google/android/exoplayer/extractor/ts/TsExtractor.java
View file @
709fc773
...
@@ -53,6 +53,7 @@ public final class TsExtractor implements Extractor, SeekMap {
...
@@ -53,6 +53,7 @@ public final class TsExtractor implements Extractor, SeekMap {
private
final
ParsableByteArray
tsPacketBuffer
;
private
final
ParsableByteArray
tsPacketBuffer
;
private
final
ParsableBitArray
tsScratch
;
private
final
ParsableBitArray
tsScratch
;
private
final
boolean
idrKeyframesOnly
;
private
final
long
firstSampleTimestampUs
;
private
final
long
firstSampleTimestampUs
;
/* package */
final
SparseBooleanArray
streamTypes
;
/* package */
final
SparseBooleanArray
streamTypes
;
/* package */
final
SparseBooleanArray
allowedPassthroughStreamTypes
;
/* package */
final
SparseBooleanArray
allowedPassthroughStreamTypes
;
...
@@ -65,11 +66,21 @@ public final class TsExtractor implements Extractor, SeekMap {
...
@@ -65,11 +66,21 @@ public final class TsExtractor implements Extractor, SeekMap {
/* package */
Id3Reader
id3Reader
;
/* package */
Id3Reader
id3Reader
;
public
TsExtractor
()
{
public
TsExtractor
()
{
this
(
0
,
null
);
this
(
0
);
}
public
TsExtractor
(
long
firstSampleTimestampUs
)
{
this
(
firstSampleTimestampUs
,
null
);
}
}
public
TsExtractor
(
long
firstSampleTimestampUs
,
AudioCapabilities
audioCapabilities
)
{
public
TsExtractor
(
long
firstSampleTimestampUs
,
AudioCapabilities
audioCapabilities
)
{
this
(
firstSampleTimestampUs
,
audioCapabilities
,
true
);
}
public
TsExtractor
(
long
firstSampleTimestampUs
,
AudioCapabilities
audioCapabilities
,
boolean
idrKeyframesOnly
)
{
this
.
firstSampleTimestampUs
=
firstSampleTimestampUs
;
this
.
firstSampleTimestampUs
=
firstSampleTimestampUs
;
this
.
idrKeyframesOnly
=
idrKeyframesOnly
;
tsScratch
=
new
ParsableBitArray
(
new
byte
[
3
]);
tsScratch
=
new
ParsableBitArray
(
new
byte
[
3
]);
tsPacketBuffer
=
new
ParsableByteArray
(
TS_PACKET_SIZE
);
tsPacketBuffer
=
new
ParsableByteArray
(
TS_PACKET_SIZE
);
streamTypes
=
new
SparseBooleanArray
();
streamTypes
=
new
SparseBooleanArray
();
...
@@ -103,6 +114,8 @@ public final class TsExtractor implements Extractor, SeekMap {
...
@@ -103,6 +114,8 @@ public final class TsExtractor implements Extractor, SeekMap {
return
RESULT_END_OF_INPUT
;
return
RESULT_END_OF_INPUT
;
}
}
// Note: see ISO/IEC 13818-1, section 2.4.3.2 for detailed information on the format of
// the header.
tsPacketBuffer
.
setPosition
(
0
);
tsPacketBuffer
.
setPosition
(
0
);
tsPacketBuffer
.
setLimit
(
TS_PACKET_SIZE
);
tsPacketBuffer
.
setLimit
(
TS_PACKET_SIZE
);
int
syncByte
=
tsPacketBuffer
.
readUnsignedByte
();
int
syncByte
=
tsPacketBuffer
.
readUnsignedByte
();
...
@@ -292,6 +305,8 @@ public final class TsExtractor implements Extractor, SeekMap {
...
@@ -292,6 +305,8 @@ public final class TsExtractor implements Extractor, SeekMap {
data
.
skipBytes
(
pointerField
);
data
.
skipBytes
(
pointerField
);
}
}
// Note: see ISO/IEC 13818-1, section 2.4.4.8 for detailed information on the format of
// the header.
data
.
readBytes
(
pmtScratch
,
3
);
data
.
readBytes
(
pmtScratch
,
3
);
pmtScratch
.
skipBits
(
12
);
// table_id (8), section_syntax_indicator (1), '0' (1), reserved (2)
pmtScratch
.
skipBits
(
12
);
// table_id (8), section_syntax_indicator (1), '0' (1), reserved (2)
int
sectionLength
=
pmtScratch
.
readBits
(
12
);
int
sectionLength
=
pmtScratch
.
readBits
(
12
);
...
@@ -347,7 +362,8 @@ public final class TsExtractor implements Extractor, SeekMap {
...
@@ -347,7 +362,8 @@ public final class TsExtractor implements Extractor, SeekMap {
break
;
break
;
case
TS_STREAM_TYPE_H264:
case
TS_STREAM_TYPE_H264:
SeiReader
seiReader
=
new
SeiReader
(
output
.
track
(
TS_STREAM_TYPE_EIA608
));
SeiReader
seiReader
=
new
SeiReader
(
output
.
track
(
TS_STREAM_TYPE_EIA608
));
pesPayloadReader
=
new
H264Reader
(
output
.
track
(
TS_STREAM_TYPE_H264
),
seiReader
);
pesPayloadReader
=
new
H264Reader
(
output
.
track
(
TS_STREAM_TYPE_H264
),
seiReader
,
idrKeyframesOnly
);
break
;
break
;
case
TS_STREAM_TYPE_ID3:
case
TS_STREAM_TYPE_ID3:
pesPayloadReader
=
id3Reader
;
pesPayloadReader
=
id3Reader
;
...
@@ -502,6 +518,8 @@ public final class TsExtractor implements Extractor, SeekMap {
...
@@ -502,6 +518,8 @@ public final class TsExtractor implements Extractor, SeekMap {
}
}
private
boolean
parseHeader
()
{
private
boolean
parseHeader
()
{
// Note: see ISO/IEC 13818-1, section 2.4.3.6 for detailed information on the format of
// the header.
pesScratch
.
setPosition
(
0
);
pesScratch
.
setPosition
(
0
);
int
startCodePrefix
=
pesScratch
.
readBits
(
24
);
int
startCodePrefix
=
pesScratch
.
readBits
(
24
);
if
(
startCodePrefix
!=
0x000001
)
{
if
(
startCodePrefix
!=
0x000001
)
{
...
@@ -534,7 +552,7 @@ public final class TsExtractor implements Extractor, SeekMap {
...
@@ -534,7 +552,7 @@ public final class TsExtractor implements Extractor, SeekMap {
pesScratch
.
setPosition
(
0
);
pesScratch
.
setPosition
(
0
);
timeUs
=
0
;
timeUs
=
0
;
if
(
ptsFlag
)
{
if
(
ptsFlag
)
{
pesScratch
.
skipBits
(
4
);
// '0010'
pesScratch
.
skipBits
(
4
);
// '0010'
or '0011'
long
pts
=
(
long
)
pesScratch
.
readBits
(
3
)
<<
30
;
long
pts
=
(
long
)
pesScratch
.
readBits
(
3
)
<<
30
;
pesScratch
.
skipBits
(
1
);
// marker_bit
pesScratch
.
skipBits
(
1
);
// marker_bit
pts
|=
pesScratch
.
readBits
(
15
)
<<
15
;
pts
|=
pesScratch
.
readBits
(
15
)
<<
15
;
...
...
library/src/main/java/com/google/android/exoplayer/upstream/MulticastDataSource.java
0 → 100644
View file @
709fc773
/*
* Copyright (C) 2014 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
com
.
google
.
android
.
exoplayer
.
upstream
;
import
com.google.android.exoplayer.C
;
import
java.io.IOException
;
import
java.net.DatagramPacket
;
import
java.net.InetAddress
;
import
java.net.MulticastSocket
;
/**
* A multicast {@link DataSource}.
*/
public
class
MulticastDataSource
implements
UriDataSource
{
/**
* Thrown when an error is encountered when trying to read from a {@link MulticastDataSource}.
*/
public
static
final
class
MulticastDataSourceException
extends
IOException
{
public
MulticastDataSourceException
(
String
message
)
{
super
(
message
);
}
public
MulticastDataSourceException
(
IOException
cause
)
{
super
(
cause
);
}
}
public
static
final
int
DEFAULT_MAX_PACKET_SIZE
=
2000
;
public
static
final
int
TRANSFER_LISTENER_PACKET_INTERVAL
=
1000
;
private
final
TransferListener
transferListener
;
private
final
DatagramPacket
packet
;
private
DataSpec
dataSpec
;
private
MulticastSocket
socket
;
private
boolean
opened
;
private
int
packetsReceived
;
private
byte
[]
packetBuffer
;
private
int
packetRemaining
;
public
MulticastDataSource
(
TransferListener
transferListener
)
{
this
(
transferListener
,
DEFAULT_MAX_PACKET_SIZE
);
}
public
MulticastDataSource
(
TransferListener
transferListener
,
int
maxPacketSize
)
{
this
.
transferListener
=
transferListener
;
packetBuffer
=
new
byte
[
maxPacketSize
];
packet
=
new
DatagramPacket
(
packetBuffer
,
0
,
maxPacketSize
);
}
@Override
public
long
open
(
DataSpec
dataSpec
)
throws
MulticastDataSourceException
{
this
.
dataSpec
=
dataSpec
;
String
uri
=
dataSpec
.
uri
.
toString
();
String
host
=
uri
.
substring
(
0
,
uri
.
indexOf
(
':'
));
int
port
=
Integer
.
parseInt
(
uri
.
substring
(
uri
.
indexOf
(
':'
)
+
1
));
try
{
socket
=
new
MulticastSocket
(
port
);
socket
.
joinGroup
(
InetAddress
.
getByName
(
host
));
}
catch
(
IOException
e
)
{
throw
new
MulticastDataSourceException
(
e
);
}
opened
=
true
;
transferListener
.
onTransferStart
();
return
C
.
LENGTH_UNBOUNDED
;
}
@Override
public
void
close
()
{
if
(
opened
)
{
socket
.
close
();
socket
=
null
;
transferListener
.
onTransferEnd
();
packetRemaining
=
0
;
packetsReceived
=
0
;
opened
=
false
;
}
}
@Override
public
int
read
(
byte
[]
buffer
,
int
offset
,
int
readLength
)
throws
MulticastDataSourceException
{
// if we've read all the data, get another packet
if
(
packetRemaining
==
0
)
{
if
(
packetsReceived
==
TRANSFER_LISTENER_PACKET_INTERVAL
)
{
transferListener
.
onTransferEnd
();
transferListener
.
onTransferStart
();
packetsReceived
=
0
;
}
try
{
socket
.
receive
(
packet
);
}
catch
(
IOException
e
)
{
throw
new
MulticastDataSourceException
(
e
);
}
packetRemaining
=
packet
.
getLength
();
transferListener
.
onBytesTransferred
(
packetRemaining
);
packetsReceived
++;
}
// don't try to read too much
if
(
packetRemaining
<
readLength
)
{
readLength
=
packetRemaining
;
}
int
packetOffset
=
packet
.
getLength
()
-
packetRemaining
;
System
.
arraycopy
(
packetBuffer
,
packetOffset
,
buffer
,
offset
,
readLength
);
packetRemaining
-=
readLength
;
return
readLength
;
}
@Override
public
String
getUri
()
{
return
dataSpec
==
null
?
null
:
dataSpec
.
uri
.
toString
();
}
}
library/src/main/java/com/google/android/exoplayer/util/ParsableBitArray.java
View file @
709fc773
...
@@ -26,6 +26,7 @@ public final class ParsableBitArray {
...
@@ -26,6 +26,7 @@ public final class ParsableBitArray {
// byte (from 0 to 7).
// byte (from 0 to 7).
private
int
byteOffset
;
private
int
byteOffset
;
private
int
bitOffset
;
private
int
bitOffset
;
private
int
byteLimit
;
/** Creates a new instance that initially has no backing data. */
/** Creates a new instance that initially has no backing data. */
public
ParsableBitArray
()
{}
public
ParsableBitArray
()
{}
...
@@ -36,7 +37,18 @@ public final class ParsableBitArray {
...
@@ -36,7 +37,18 @@ public final class ParsableBitArray {
* @param data The data to wrap.
* @param data The data to wrap.
*/
*/
public
ParsableBitArray
(
byte
[]
data
)
{
public
ParsableBitArray
(
byte
[]
data
)
{
this
(
data
,
data
.
length
);
}
/**
* Creates a new instance that wraps an existing array.
*
* @param data The data to wrap.
* @param limit The limit in bytes.
*/
public
ParsableBitArray
(
byte
[]
data
,
int
limit
)
{
this
.
data
=
data
;
this
.
data
=
data
;
byteLimit
=
limit
;
}
}
/**
/**
...
@@ -45,9 +57,27 @@ public final class ParsableBitArray {
...
@@ -45,9 +57,27 @@ public final class ParsableBitArray {
* @param data The array to wrap.
* @param data The array to wrap.
*/
*/
public
void
reset
(
byte
[]
data
)
{
public
void
reset
(
byte
[]
data
)
{
reset
(
data
,
data
.
length
);
}
/**
* Updates the instance to wrap {@code data}, and resets the position to zero.
*
* @param data The array to wrap.
* @param limit The limit in bytes.
*/
public
void
reset
(
byte
[]
data
,
int
limit
)
{
this
.
data
=
data
;
this
.
data
=
data
;
byteOffset
=
0
;
byteOffset
=
0
;
bitOffset
=
0
;
bitOffset
=
0
;
byteLimit
=
limit
;
}
/**
* Returns the number of bits yet to be read.
*/
public
int
bitsLeft
()
{
return
(
byteLimit
-
byteOffset
)
*
8
-
bitOffset
;
}
}
/**
/**
...
@@ -67,6 +97,7 @@ public final class ParsableBitArray {
...
@@ -67,6 +97,7 @@ public final class ParsableBitArray {
public
void
setPosition
(
int
position
)
{
public
void
setPosition
(
int
position
)
{
byteOffset
=
position
/
8
;
byteOffset
=
position
/
8
;
bitOffset
=
position
-
(
byteOffset
*
8
);
bitOffset
=
position
-
(
byteOffset
*
8
);
assertValidOffset
();
}
}
/**
/**
...
@@ -81,6 +112,7 @@ public final class ParsableBitArray {
...
@@ -81,6 +112,7 @@ public final class ParsableBitArray {
byteOffset
++;
byteOffset
++;
bitOffset
-=
8
;
bitOffset
-=
8
;
}
}
assertValidOffset
();
}
}
/**
/**
...
@@ -103,12 +135,20 @@ public final class ParsableBitArray {
...
@@ -103,12 +135,20 @@ public final class ParsableBitArray {
return
0
;
return
0
;
}
}
int
ret
val
=
0
;
int
ret
urnValue
=
0
;
// While n >= 8, read whole bytes.
// While n >= 8, read whole bytes.
while
(
n
>=
8
)
{
while
(
n
>=
8
)
{
int
byteValue
;
if
(
bitOffset
!=
0
)
{
byteValue
=
((
data
[
byteOffset
]
&
0xFF
)
<<
bitOffset
)
|
((
data
[
byteOffset
+
1
]
&
0xFF
)
>>>
(
8
-
bitOffset
));
}
else
{
byteValue
=
data
[
byteOffset
];
}
n
-=
8
;
n
-=
8
;
retval
|=
(
readUnsignedByte
()
<<
n
);
returnValue
|=
(
byteValue
&
0xFF
)
<<
n
;
byteOffset
++;
}
}
if
(
n
>
0
)
{
if
(
n
>
0
)
{
...
@@ -117,12 +157,12 @@ public final class ParsableBitArray {
...
@@ -117,12 +157,12 @@ public final class ParsableBitArray {
if
(
nextBit
>
8
)
{
if
(
nextBit
>
8
)
{
// Combine bits from current byte and next byte.
// Combine bits from current byte and next byte.
ret
val
|=
(((
getUnsignedByte
(
byteOffset
)
<<
(
nextBit
-
8
)
ret
urnValue
|=
((((
data
[
byteOffset
]
&
0xFF
)
<<
(
nextBit
-
8
)
|
(
getUnsignedByte
(
byteOffset
+
1
)
>>
(
16
-
nextBit
)))
&
writeMask
));
|
(
(
data
[
byteOffset
+
1
]
&
0xFF
)
>>
(
16
-
nextBit
)))
&
writeMask
));
byteOffset
++;
byteOffset
++;
}
else
{
}
else
{
// Bits to be read only within current byte.
// Bits to be read only within current byte.
ret
val
|=
((
getUnsignedByte
(
byteOffset
)
>>
(
8
-
nextBit
))
&
writeMask
);
ret
urnValue
|=
(((
data
[
byteOffset
]
&
0xFF
)
>>
(
8
-
nextBit
))
&
writeMask
);
if
(
nextBit
==
8
)
{
if
(
nextBit
==
8
)
{
byteOffset
++;
byteOffset
++;
}
}
...
@@ -131,7 +171,27 @@ public final class ParsableBitArray {
...
@@ -131,7 +171,27 @@ public final class ParsableBitArray {
bitOffset
=
nextBit
%
8
;
bitOffset
=
nextBit
%
8
;
}
}
return
retval
;
assertValidOffset
();
return
returnValue
;
}
/**
* Peeks the length of an Exp-Golomb-coded integer (signed or unsigned) starting from the current
* offset, returning the length or -1 if the limit is reached.
*
* @return The length of the Exp-Golob-coded integer, or -1.
*/
public
int
peekExpGolombCodedNumLength
()
{
int
initialByteOffset
=
byteOffset
;
int
initialBitOffset
=
bitOffset
;
int
leadingZeros
=
0
;
while
(
byteOffset
<
byteLimit
&&
!
readBit
())
{
leadingZeros
++;
}
boolean
hitLimit
=
byteOffset
==
byteLimit
;
byteOffset
=
initialByteOffset
;
bitOffset
=
initialBitOffset
;
return
hitLimit
?
-
1
:
leadingZeros
*
2
+
1
;
}
}
/**
/**
...
@@ -153,22 +213,6 @@ public final class ParsableBitArray {
...
@@ -153,22 +213,6 @@ public final class ParsableBitArray {
return
((
codeNum
%
2
)
==
0
?
-
1
:
1
)
*
((
codeNum
+
1
)
/
2
);
return
((
codeNum
%
2
)
==
0
?
-
1
:
1
)
*
((
codeNum
+
1
)
/
2
);
}
}
private
int
readUnsignedByte
()
{
int
value
;
if
(
bitOffset
!=
0
)
{
value
=
((
data
[
byteOffset
]
&
0xFF
)
<<
bitOffset
)
|
((
data
[
byteOffset
+
1
]
&
0xFF
)
>>>
(
8
-
bitOffset
));
}
else
{
value
=
data
[
byteOffset
];
}
byteOffset
++;
return
value
&
0xFF
;
}
private
int
getUnsignedByte
(
int
offset
)
{
return
data
[
offset
]
&
0xFF
;
}
private
int
readExpGolombCodeNum
()
{
private
int
readExpGolombCodeNum
()
{
int
leadingZeros
=
0
;
int
leadingZeros
=
0
;
while
(!
readBit
())
{
while
(!
readBit
())
{
...
@@ -177,4 +221,11 @@ public final class ParsableBitArray {
...
@@ -177,4 +221,11 @@ public final class ParsableBitArray {
return
(
1
<<
leadingZeros
)
-
1
+
(
leadingZeros
>
0
?
readBits
(
leadingZeros
)
:
0
);
return
(
1
<<
leadingZeros
)
-
1
+
(
leadingZeros
>
0
?
readBits
(
leadingZeros
)
:
0
);
}
}
private
void
assertValidOffset
()
{
// It is fine for position to be at the end of the array, but no further.
Assertions
.
checkState
(
byteOffset
>=
0
&&
(
bitOffset
>=
0
&&
bitOffset
<
8
)
&&
(
byteOffset
<
byteLimit
||
(
byteOffset
==
byteLimit
&&
bitOffset
==
0
)));
}
}
}
library/src/main/java/com/google/android/exoplayer/util/Util.java
View file @
709fc773
...
@@ -542,6 +542,22 @@ public final class Util {
...
@@ -542,6 +542,22 @@ public final class Util {
}
}
/**
/**
* Returns a hex string representation of the data provided.
*
* @param data The byte array containing the data to be turned into a hex string.
* @param beginIndex The begin index, inclusive.
* @param endIndex The end index, exclusive.
* @return A string containing the hex representation of the data provided.
*/
public
static
String
getHexStringFromBytes
(
byte
[]
data
,
int
beginIndex
,
int
endIndex
)
{
StringBuffer
dataStringBuffer
=
new
StringBuffer
(
endIndex
-
beginIndex
);
for
(
int
i
=
beginIndex
;
i
<
endIndex
;
i
++)
{
dataStringBuffer
.
append
(
String
.
format
(
"%02X"
,
data
[
i
]));
}
return
dataStringBuffer
.
toString
();
}
/**
* Returns a user agent string based on the given application name and the library version.
* Returns a user agent string based on the given application name and the library version.
*
*
* @param context A valid context of the calling application.
* @param context A valid context of the calling application.
...
...
library/src/test/java/com/google/android/exoplayer/TimeRangeTest.java
0 → 100644
View file @
709fc773
/*
* Copyright (C) 2014 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
com
.
google
.
android
.
exoplayer
;
import
junit.framework.TestCase
;
/**
* Unit test for {@link TimeRange}.
*/
public
class
TimeRangeTest
extends
TestCase
{
public
void
testEquals
()
{
TimeRange
timeRange1
=
new
TimeRange
(
TimeRange
.
TYPE_SNAPSHOT
,
0
,
30000000
);
assertTrue
(
timeRange1
.
equals
(
timeRange1
));
TimeRange
timeRange2
=
new
TimeRange
(
TimeRange
.
TYPE_SNAPSHOT
,
0
,
30000000
);
assertTrue
(
timeRange1
.
equals
(
timeRange2
));
TimeRange
timeRange3
=
new
TimeRange
(
TimeRange
.
TYPE_SNAPSHOT
,
0
,
60000000
);
assertFalse
(
timeRange1
.
equals
(
timeRange3
));
}
}
library/src/test/java/com/google/android/exoplayer/dash/DashChunkSourceTest.java
View file @
709fc773
...
@@ -15,9 +15,11 @@
...
@@ -15,9 +15,11 @@
*/
*/
package
com
.
google
.
android
.
exoplayer
.
dash
;
package
com
.
google
.
android
.
exoplayer
.
dash
;
import
static
org
.
mockito
.
Mockito
.
mock
;
import
static
org
.
mockito
.
Mockito
.
when
;
import
static
org
.
mockito
.
Mockito
.
when
;
import
com.google.android.exoplayer.MediaFormat
;
import
com.google.android.exoplayer.MediaFormat
;
import
com.google.android.exoplayer.TimeRange
;
import
com.google.android.exoplayer.TrackRenderer
;
import
com.google.android.exoplayer.TrackRenderer
;
import
com.google.android.exoplayer.chunk.ChunkOperationHolder
;
import
com.google.android.exoplayer.chunk.ChunkOperationHolder
;
import
com.google.android.exoplayer.chunk.Format
;
import
com.google.android.exoplayer.chunk.Format
;
...
@@ -55,12 +57,19 @@ public class DashChunkSourceTest extends InstrumentationTestCase {
...
@@ -55,12 +57,19 @@ public class DashChunkSourceTest extends InstrumentationTestCase {
private
static
final
FormatEvaluator
EVALUATOR
=
new
FixedEvaluator
();
private
static
final
FormatEvaluator
EVALUATOR
=
new
FixedEvaluator
();
private
static
final
long
AVAILABILITY_START_TIME
=
0
;
private
static
final
long
VOD_DURATION
=
30000
;
private
static
final
long
AVAILABILITY_LATENCY
=
5000
;
private
static
final
long
AVAILABILITY_REALTIME_OFFSET
=
1000
;
private
static
final
long
LIVE_SEGMENT_COUNT
=
5
;
private
static
final
long
AVAILABILITY_CURRENT_TIME
=
private
static
final
long
LIVE_SEGMENT_DURATION_MS
=
1000
;
AVAILABILITY_START_TIME
+
AVAILABILITY_LATENCY
-
AVAILABILITY_REALTIME_OFFSET
;
private
static
final
long
LIVE_TIMESHIFT_BUFFER_DEPTH_MS
=
private
static
final
FakeClock
AVAILABILITY_CLOCK
=
new
FakeClock
(
AVAILABILITY_CURRENT_TIME
);
LIVE_SEGMENT_COUNT
*
LIVE_SEGMENT_DURATION_MS
;
private
static
final
long
AVAILABILITY_START_TIME_MS
=
60000
;
private
static
final
long
AVAILABILITY_REALTIME_OFFSET_MS
=
1000
;
private
static
final
long
AVAILABILITY_CURRENT_TIME_MS
=
AVAILABILITY_START_TIME_MS
+
LIVE_TIMESHIFT_BUFFER_DEPTH_MS
-
AVAILABILITY_REALTIME_OFFSET_MS
;
private
static
final
long
LIVE_SEEK_BEYOND_EDGE_MS
=
60000
;
private
static
final
int
TALL_HEIGHT
=
200
;
private
static
final
int
TALL_HEIGHT
=
200
;
private
static
final
int
WIDE_WIDTH
=
400
;
private
static
final
int
WIDE_WIDTH
=
400
;
...
@@ -90,6 +99,21 @@ public class DashChunkSourceTest extends InstrumentationTestCase {
...
@@ -90,6 +99,21 @@ public class DashChunkSourceTest extends InstrumentationTestCase {
assertEquals
(
TALL_HEIGHT
,
out
.
getMaxVideoHeight
());
assertEquals
(
TALL_HEIGHT
,
out
.
getMaxVideoHeight
());
}
}
public
void
testGetSeekRangeOnVod
()
{
DashChunkSource
chunkSource
=
new
DashChunkSource
(
generateVodMpd
(),
AdaptationSet
.
TYPE_VIDEO
,
null
,
null
,
mock
(
FormatEvaluator
.
class
));
chunkSource
.
enable
();
TimeRange
seekRange
=
chunkSource
.
getSeekRange
();
long
[]
seekRangeValuesUs
=
seekRange
.
getCurrentBoundsUs
(
null
);
assertEquals
(
0
,
seekRangeValuesUs
[
0
]);
assertEquals
(
VOD_DURATION
*
1000
,
seekRangeValuesUs
[
1
]);
long
[]
seekRangeValuesMs
=
seekRange
.
getCurrentBoundsMs
(
null
);
assertEquals
(
0
,
seekRangeValuesMs
[
0
]);
assertEquals
(
VOD_DURATION
,
seekRangeValuesMs
[
1
]);
}
public
void
testMaxVideoDimensionsLegacy
()
{
public
void
testMaxVideoDimensionsLegacy
()
{
SingleSegmentBase
segmentBase1
=
new
SingleSegmentBase
(
"https://example.com/1.mp4"
);
SingleSegmentBase
segmentBase1
=
new
SingleSegmentBase
(
"https://example.com/1.mp4"
);
Representation
representation1
=
Representation
representation1
=
...
@@ -107,147 +131,254 @@ public class DashChunkSourceTest extends InstrumentationTestCase {
...
@@ -107,147 +131,254 @@ public class DashChunkSourceTest extends InstrumentationTestCase {
assertEquals
(
TALL_HEIGHT
,
out
.
getMaxVideoHeight
());
assertEquals
(
TALL_HEIGHT
,
out
.
getMaxVideoHeight
());
}
}
public
void
testLiveEdgeNoLatencyWithTimeline
()
{
public
void
testLiveEdgeNoLatency
()
{
DashChunkSource
chunkSource
=
setupLiveEdgeTimelineTest
(
0L
);
long
startTimeMs
=
0
;
List
<
MediaChunk
>
queue
=
new
ArrayList
<
MediaChunk
>();
long
liveEdgeLatencyMs
=
0
;
ChunkOperationHolder
out
=
new
ChunkOperationHolder
();
long
seekPositionMs
=
LIVE_SEEK_BEYOND_EDGE_MS
;
chunkSource
.
getChunkOperation
(
queue
,
0
,
0
,
out
);
long
seekRangeStartMs
=
0
;
long
seekRangeEndMs
=
LIVE_SEGMENT_COUNT
*
LIVE_SEGMENT_DURATION_MS
-
liveEdgeLatencyMs
;
assertEquals
(
4000000L
,
((
MediaChunk
)
out
.
chunk
).
startTimeUs
);
long
chunkStartTimeMs
=
4000
;
assertEquals
(
5000000L
,
((
MediaChunk
)
out
.
chunk
).
endTimeUs
);
long
chunkEndTimeMs
=
5000
;
checkLiveEdgeLatencyWithTimeline
(
startTimeMs
,
liveEdgeLatencyMs
,
seekPositionMs
,
seekRangeStartMs
,
seekRangeEndMs
,
chunkStartTimeMs
,
chunkEndTimeMs
);
checkLiveEdgeLatencyWithTemplateAndUnlimitedTimeshift
(
startTimeMs
,
liveEdgeLatencyMs
,
seekPositionMs
,
seekRangeEndMs
,
chunkStartTimeMs
,
chunkEndTimeMs
);
checkLiveEdgeLatencyWithTemplateAndLimitedTimeshift
(
startTimeMs
,
liveEdgeLatencyMs
,
seekPositionMs
,
seekRangeStartMs
,
seekRangeEndMs
,
chunkStartTimeMs
,
chunkEndTimeMs
);
}
}
public
void
testLiveEdge500msLatencyWithTimeline
()
{
public
void
testLiveEdgeAlmostNoLatency
()
{
DashChunkSource
chunkSource
=
setupLiveEdgeTimelineTest
(
500L
);
long
startTimeMs
=
0
;
List
<
MediaChunk
>
queue
=
new
ArrayList
<
MediaChunk
>();
long
liveEdgeLatencyMs
=
1
;
ChunkOperationHolder
out
=
new
ChunkOperationHolder
();
long
seekPositionMs
=
LIVE_SEEK_BEYOND_EDGE_MS
;
chunkSource
.
getChunkOperation
(
queue
,
0
,
0
,
out
);
long
seekRangeStartMs
=
0
;
long
seekRangeEndMs
=
LIVE_SEGMENT_COUNT
*
LIVE_SEGMENT_DURATION_MS
-
liveEdgeLatencyMs
;
assertEquals
(
4000000L
,
((
MediaChunk
)
out
.
chunk
).
startTimeUs
);
long
chunkStartTimeMs
=
4000
;
assertEquals
(
5000000L
,
((
MediaChunk
)
out
.
chunk
).
endTimeUs
);
long
chunkEndTimeMs
=
5000
;
checkLiveEdgeLatencyWithTimeline
(
startTimeMs
,
liveEdgeLatencyMs
,
seekPositionMs
,
seekRangeStartMs
,
seekRangeEndMs
,
chunkStartTimeMs
,
chunkEndTimeMs
);
checkLiveEdgeLatencyWithTemplateAndUnlimitedTimeshift
(
startTimeMs
,
liveEdgeLatencyMs
,
seekPositionMs
,
seekRangeEndMs
,
chunkStartTimeMs
,
chunkEndTimeMs
);
checkLiveEdgeLatencyWithTemplateAndLimitedTimeshift
(
startTimeMs
,
liveEdgeLatencyMs
,
seekPositionMs
,
seekRangeStartMs
,
seekRangeEndMs
,
chunkStartTimeMs
,
chunkEndTimeMs
);
}
}
public
void
testLiveEdge1000msLatencyWithTimeline
()
{
public
void
testLiveEdge500msLatency
()
{
DashChunkSource
chunkSource
=
setupLiveEdgeTimelineTest
(
1000L
);
long
startTimeMs
=
0
;
List
<
MediaChunk
>
queue
=
new
ArrayList
<
MediaChunk
>();
long
liveEdgeLatencyMs
=
500
;
ChunkOperationHolder
out
=
new
ChunkOperationHolder
();
long
seekPositionMs
=
LIVE_SEEK_BEYOND_EDGE_MS
;
chunkSource
.
getChunkOperation
(
queue
,
0
,
0
,
out
);
long
seekRangeStartMs
=
0
;
long
seekRangeEndMs
=
LIVE_SEGMENT_COUNT
*
LIVE_SEGMENT_DURATION_MS
-
liveEdgeLatencyMs
;
assertEquals
(
4000000L
,
((
MediaChunk
)
out
.
chunk
).
startTimeUs
);
long
chunkStartTimeMs
=
4000
;
assertEquals
(
5000000L
,
((
MediaChunk
)
out
.
chunk
).
endTimeUs
);
long
chunkEndTimeMs
=
5000
;
checkLiveEdgeLatencyWithTimeline
(
startTimeMs
,
liveEdgeLatencyMs
,
seekPositionMs
,
seekRangeStartMs
,
seekRangeEndMs
,
chunkStartTimeMs
,
chunkEndTimeMs
);
checkLiveEdgeLatencyWithTemplateAndUnlimitedTimeshift
(
startTimeMs
,
liveEdgeLatencyMs
,
seekPositionMs
,
seekRangeEndMs
,
chunkStartTimeMs
,
chunkEndTimeMs
);
checkLiveEdgeLatencyWithTemplateAndLimitedTimeshift
(
startTimeMs
,
liveEdgeLatencyMs
,
seekPositionMs
,
seekRangeStartMs
,
seekRangeEndMs
,
chunkStartTimeMs
,
chunkEndTimeMs
);
}
}
public
void
testLiveEdge1001msLatencyWithTimeline
()
{
public
void
testLiveEdge1000msLatency
()
{
DashChunkSource
chunkSource
=
setupLiveEdgeTimelineTest
(
1001L
);
long
startTimeMs
=
0
;
List
<
MediaChunk
>
queue
=
new
ArrayList
<
MediaChunk
>();
long
liveEdgeLatencyMs
=
1000
;
ChunkOperationHolder
out
=
new
ChunkOperationHolder
();
long
seekPositionMs
=
LIVE_SEEK_BEYOND_EDGE_MS
;
chunkSource
.
getChunkOperation
(
queue
,
0
,
0
,
out
);
long
seekRangeStartMs
=
0
;
long
seekRangeEndMs
=
LIVE_SEGMENT_COUNT
*
LIVE_SEGMENT_DURATION_MS
-
liveEdgeLatencyMs
;
assertEquals
(
3000000L
,
((
MediaChunk
)
out
.
chunk
).
startTimeUs
);
long
chunkStartTimeMs
=
4000
;
assertEquals
(
4000000L
,
((
MediaChunk
)
out
.
chunk
).
endTimeUs
);
long
chunkEndTimeMs
=
5000
;
checkLiveEdgeLatencyWithTimeline
(
startTimeMs
,
liveEdgeLatencyMs
,
seekPositionMs
,
seekRangeStartMs
,
seekRangeEndMs
,
chunkStartTimeMs
,
chunkEndTimeMs
);
checkLiveEdgeLatencyWithTemplateAndUnlimitedTimeshift
(
startTimeMs
,
liveEdgeLatencyMs
,
seekPositionMs
,
seekRangeEndMs
,
chunkStartTimeMs
,
chunkEndTimeMs
);
checkLiveEdgeLatencyWithTemplateAndLimitedTimeshift
(
startTimeMs
,
liveEdgeLatencyMs
,
seekPositionMs
,
seekRangeStartMs
,
seekRangeEndMs
,
chunkStartTimeMs
,
chunkEndTimeMs
);
}
}
public
void
testLiveEdge2500msLatencyWithTimeline
()
{
public
void
testLiveEdge1001msLatency
()
{
DashChunkSource
chunkSource
=
setupLiveEdgeTimelineTest
(
2500L
);
long
startTimeMs
=
0
;
List
<
MediaChunk
>
queue
=
new
ArrayList
<
MediaChunk
>();
long
liveEdgeLatencyMs
=
1001
;
ChunkOperationHolder
out
=
new
ChunkOperationHolder
();
long
seekPositionMs
=
LIVE_SEEK_BEYOND_EDGE_MS
;
chunkSource
.
getChunkOperation
(
queue
,
0
,
0
,
out
);
long
seekRangeStartMs
=
0
;
long
seekRangeEndMs
=
LIVE_SEGMENT_COUNT
*
LIVE_SEGMENT_DURATION_MS
-
liveEdgeLatencyMs
;
assertEquals
(
2000000L
,
((
MediaChunk
)
out
.
chunk
).
startTimeUs
);
long
chunkStartTimeMs
=
3000
;
assertEquals
(
3000000L
,
((
MediaChunk
)
out
.
chunk
).
endTimeUs
);
long
chunkEndTimeMs
=
4000
;
checkLiveEdgeLatencyWithTimeline
(
startTimeMs
,
liveEdgeLatencyMs
,
seekPositionMs
,
seekRangeStartMs
,
seekRangeEndMs
,
chunkStartTimeMs
,
chunkEndTimeMs
);
checkLiveEdgeLatencyWithTemplateAndUnlimitedTimeshift
(
startTimeMs
,
liveEdgeLatencyMs
,
seekPositionMs
,
seekRangeEndMs
,
chunkStartTimeMs
,
chunkEndTimeMs
);
checkLiveEdgeLatencyWithTemplateAndLimitedTimeshift
(
startTimeMs
,
liveEdgeLatencyMs
,
seekPositionMs
,
seekRangeStartMs
,
seekRangeEndMs
,
chunkStartTimeMs
,
chunkEndTimeMs
);
}
}
public
void
testLiveEdgeVeryHighLatencyWithTimeline
()
{
public
void
testLiveEdge2500msLatency
()
{
DashChunkSource
chunkSource
=
setupLiveEdgeTimelineTest
(
10000L
);
long
startTimeMs
=
0
;
List
<
MediaChunk
>
queue
=
new
ArrayList
<
MediaChunk
>();
long
liveEdgeLatencyMs
=
2500
;
ChunkOperationHolder
out
=
new
ChunkOperationHolder
();
long
seekPositionMs
=
LIVE_SEEK_BEYOND_EDGE_MS
;
chunkSource
.
getChunkOperation
(
queue
,
0
,
0
,
out
);
long
seekRangeStartMs
=
0
;
long
seekRangeEndMs
=
LIVE_SEGMENT_COUNT
*
LIVE_SEGMENT_DURATION_MS
-
liveEdgeLatencyMs
;
assertEquals
(
0L
,
((
MediaChunk
)
out
.
chunk
).
startTimeUs
);
long
chunkStartTimeMs
=
2000
;
assertEquals
(
1000000L
,
((
MediaChunk
)
out
.
chunk
).
endTimeUs
);
long
chunkEndTimeMs
=
3000
;
checkLiveEdgeLatencyWithTimeline
(
startTimeMs
,
liveEdgeLatencyMs
,
seekPositionMs
,
seekRangeStartMs
,
seekRangeEndMs
,
chunkStartTimeMs
,
chunkEndTimeMs
);
checkLiveEdgeLatencyWithTemplateAndUnlimitedTimeshift
(
startTimeMs
,
liveEdgeLatencyMs
,
seekPositionMs
,
seekRangeEndMs
,
chunkStartTimeMs
,
chunkEndTimeMs
);
checkLiveEdgeLatencyWithTemplateAndLimitedTimeshift
(
startTimeMs
,
liveEdgeLatencyMs
,
seekPositionMs
,
seekRangeStartMs
,
seekRangeEndMs
,
chunkStartTimeMs
,
chunkEndTimeMs
);
}
}
public
void
testLiveEdgeNoLatencyWithTemplate
()
{
public
void
testLiveEdgeVeryHighLatency
()
{
DashChunkSource
chunkSource
=
setupLiveEdgeTemplateTest
(
0L
);
long
startTimeMs
=
0
;
List
<
MediaChunk
>
queue
=
new
ArrayList
<
MediaChunk
>();
long
liveEdgeLatencyMs
=
10000
;
ChunkOperationHolder
out
=
new
ChunkOperationHolder
();
long
seekPositionMs
=
LIVE_SEEK_BEYOND_EDGE_MS
;
chunkSource
.
getChunkOperation
(
queue
,
0
,
0
,
out
);
long
seekRangeStartMs
=
0
;
long
seekRangeEndMs
=
0
;
// this should actually return the "5th" segment, but it currently returns the "6th", which
long
chunkStartTimeMs
=
0
;
// doesn't actually exist yet; this will be resolved in a subsequent cl (cl/87518875).
long
chunkEndTimeMs
=
1000
;
//assertEquals(4000000L, ((MediaChunk) out.chunk).startTimeUs);
//assertEquals(5000000L, ((MediaChunk) out.chunk).endTimeUs);
checkLiveEdgeLatencyWithTimeline
(
startTimeMs
,
liveEdgeLatencyMs
,
seekPositionMs
,
seekRangeStartMs
,
seekRangeEndMs
,
chunkStartTimeMs
,
chunkEndTimeMs
);
checkLiveEdgeLatencyWithTemplateAndUnlimitedTimeshift
(
startTimeMs
,
liveEdgeLatencyMs
,
seekPositionMs
,
seekRangeEndMs
,
chunkStartTimeMs
,
chunkEndTimeMs
);
checkLiveEdgeLatencyWithTemplateAndLimitedTimeshift
(
startTimeMs
,
liveEdgeLatencyMs
,
seekPositionMs
,
seekRangeStartMs
,
seekRangeEndMs
,
chunkStartTimeMs
,
chunkEndTimeMs
);
}
}
public
void
testLiveEdgeAlmostNoLatencyWithTemplate
()
{
public
void
testLiveEdgeNoLatencyInProgress
()
{
DashChunkSource
chunkSource
=
setupLiveEdgeTemplateTest
(
1L
);
long
startTimeMs
=
3000
;
List
<
MediaChunk
>
queue
=
new
ArrayList
<
MediaChunk
>();
long
liveEdgeLatencyMs
=
0
;
ChunkOperationHolder
out
=
new
ChunkOperationHolder
();
long
seekPositionMs
=
LIVE_SEEK_BEYOND_EDGE_MS
;
chunkSource
.
getChunkOperation
(
queue
,
0
,
0
,
out
);
long
seekRangeStartMs
=
3000
;
long
seekRangeEndMs
=
3000
+
LIVE_SEGMENT_COUNT
*
LIVE_SEGMENT_DURATION_MS
-
liveEdgeLatencyMs
;
assertEquals
(
4000000L
,
((
MediaChunk
)
out
.
chunk
).
startTimeUs
);
long
chunkStartTimeMs
=
7000
;
assertEquals
(
5000000L
,
((
MediaChunk
)
out
.
chunk
).
endTimeUs
);
long
chunkEndTimeMs
=
8000
;
checkLiveEdgeLatencyWithTimeline
(
startTimeMs
,
liveEdgeLatencyMs
,
seekPositionMs
,
seekRangeStartMs
,
seekRangeEndMs
,
chunkStartTimeMs
,
chunkEndTimeMs
);
checkLiveEdgeLatencyWithTemplateAndUnlimitedTimeshift
(
startTimeMs
,
liveEdgeLatencyMs
,
seekPositionMs
,
seekRangeEndMs
,
chunkStartTimeMs
,
chunkEndTimeMs
);
checkLiveEdgeLatencyWithTemplateAndLimitedTimeshift
(
startTimeMs
,
liveEdgeLatencyMs
,
seekPositionMs
,
seekRangeStartMs
,
seekRangeEndMs
,
chunkStartTimeMs
,
chunkEndTimeMs
);
}
}
public
void
testLiveEdge500msLatencyWithTemplate
()
{
public
void
testLiveEdgeAlmostNoLatencyInProgress
()
{
DashChunkSource
chunkSource
=
setupLiveEdgeTemplateTest
(
500L
);
long
startTimeMs
=
3000
;
List
<
MediaChunk
>
queue
=
new
ArrayList
<
MediaChunk
>();
long
liveEdgeLatencyMs
=
1
;
ChunkOperationHolder
out
=
new
ChunkOperationHolder
();
long
seekPositionMs
=
LIVE_SEEK_BEYOND_EDGE_MS
;
chunkSource
.
getChunkOperation
(
queue
,
0
,
0
,
out
);
long
seekRangeStartMs
=
3000
;
long
seekRangeEndMs
=
3000
+
LIVE_SEGMENT_COUNT
*
LIVE_SEGMENT_DURATION_MS
-
liveEdgeLatencyMs
;
assertEquals
(
4000000L
,
((
MediaChunk
)
out
.
chunk
).
startTimeUs
);
long
chunkStartTimeMs
=
7000
;
assertEquals
(
5000000L
,
((
MediaChunk
)
out
.
chunk
).
endTimeUs
);
long
chunkEndTimeMs
=
8000
;
checkLiveEdgeLatencyWithTimeline
(
startTimeMs
,
liveEdgeLatencyMs
,
seekPositionMs
,
seekRangeStartMs
,
seekRangeEndMs
,
chunkStartTimeMs
,
chunkEndTimeMs
);
checkLiveEdgeLatencyWithTemplateAndUnlimitedTimeshift
(
startTimeMs
,
liveEdgeLatencyMs
,
seekPositionMs
,
seekRangeEndMs
,
chunkStartTimeMs
,
chunkEndTimeMs
);
checkLiveEdgeLatencyWithTemplateAndLimitedTimeshift
(
startTimeMs
,
liveEdgeLatencyMs
,
seekPositionMs
,
seekRangeStartMs
,
seekRangeEndMs
,
chunkStartTimeMs
,
chunkEndTimeMs
);
}
}
public
void
testLiveEdge1000msLatencyWithTemplate
()
{
public
void
testLiveEdge500msLatencyInProgress
()
{
DashChunkSource
chunkSource
=
setupLiveEdgeTemplateTest
(
1000L
);
long
startTimeMs
=
3000
;
List
<
MediaChunk
>
queue
=
new
ArrayList
<
MediaChunk
>();
long
liveEdgeLatencyMs
=
500
;
ChunkOperationHolder
out
=
new
ChunkOperationHolder
();
long
seekPositionMs
=
LIVE_SEEK_BEYOND_EDGE_MS
;
chunkSource
.
getChunkOperation
(
queue
,
0
,
0
,
out
);
long
seekRangeStartMs
=
3000
;
long
seekRangeEndMs
=
3000
+
LIVE_SEGMENT_COUNT
*
LIVE_SEGMENT_DURATION_MS
-
liveEdgeLatencyMs
;
assertEquals
(
4000000L
,
((
MediaChunk
)
out
.
chunk
).
startTimeUs
);
long
chunkStartTimeMs
=
7000
;
assertEquals
(
5000000L
,
((
MediaChunk
)
out
.
chunk
).
endTimeUs
);
long
chunkEndTimeMs
=
8000
;
checkLiveEdgeLatencyWithTimeline
(
startTimeMs
,
liveEdgeLatencyMs
,
seekPositionMs
,
seekRangeStartMs
,
seekRangeEndMs
,
chunkStartTimeMs
,
chunkEndTimeMs
);
checkLiveEdgeLatencyWithTemplateAndUnlimitedTimeshift
(
startTimeMs
,
liveEdgeLatencyMs
,
seekPositionMs
,
seekRangeEndMs
,
chunkStartTimeMs
,
chunkEndTimeMs
);
checkLiveEdgeLatencyWithTemplateAndLimitedTimeshift
(
startTimeMs
,
liveEdgeLatencyMs
,
seekPositionMs
,
seekRangeStartMs
,
seekRangeEndMs
,
chunkStartTimeMs
,
chunkEndTimeMs
);
}
}
public
void
testLiveEdge1001msLatencyWithTemplate
()
{
public
void
testLiveEdge1000msLatencyInProgress
()
{
DashChunkSource
chunkSource
=
setupLiveEdgeTemplateTest
(
1001L
);
long
startTimeMs
=
3000
;
List
<
MediaChunk
>
queue
=
new
ArrayList
<
MediaChunk
>();
long
liveEdgeLatencyMs
=
1000
;
ChunkOperationHolder
out
=
new
ChunkOperationHolder
();
long
seekPositionMs
=
LIVE_SEEK_BEYOND_EDGE_MS
;
chunkSource
.
getChunkOperation
(
queue
,
0
,
0
,
out
);
long
seekRangeStartMs
=
3000
;
long
seekRangeEndMs
=
3000
+
LIVE_SEGMENT_COUNT
*
LIVE_SEGMENT_DURATION_MS
-
liveEdgeLatencyMs
;
assertEquals
(
3000000L
,
((
MediaChunk
)
out
.
chunk
).
startTimeUs
);
long
chunkStartTimeMs
=
7000
;
assertEquals
(
4000000L
,
((
MediaChunk
)
out
.
chunk
).
endTimeUs
);
long
chunkEndTimeMs
=
8000
;
checkLiveEdgeLatencyWithTimeline
(
startTimeMs
,
liveEdgeLatencyMs
,
seekPositionMs
,
seekRangeStartMs
,
seekRangeEndMs
,
chunkStartTimeMs
,
chunkEndTimeMs
);
checkLiveEdgeLatencyWithTemplateAndUnlimitedTimeshift
(
startTimeMs
,
liveEdgeLatencyMs
,
seekPositionMs
,
seekRangeEndMs
,
chunkStartTimeMs
,
chunkEndTimeMs
);
checkLiveEdgeLatencyWithTemplateAndLimitedTimeshift
(
startTimeMs
,
liveEdgeLatencyMs
,
seekPositionMs
,
seekRangeStartMs
,
seekRangeEndMs
,
chunkStartTimeMs
,
chunkEndTimeMs
);
}
}
public
void
testLiveEdge2500msLatencyWithTemplate
()
{
public
void
testLiveEdge1001msLatencyInProgress
()
{
DashChunkSource
chunkSource
=
setupLiveEdgeTemplateTest
(
2500L
);
long
startTimeMs
=
3000
;
List
<
MediaChunk
>
queue
=
new
ArrayList
<
MediaChunk
>();
long
liveEdgeLatencyMs
=
1001
;
ChunkOperationHolder
out
=
new
ChunkOperationHolder
();
long
seekPositionMs
=
LIVE_SEEK_BEYOND_EDGE_MS
;
chunkSource
.
getChunkOperation
(
queue
,
0
,
0
,
out
);
long
seekRangeStartMs
=
3000
;
long
seekRangeEndMs
=
3000
+
LIVE_SEGMENT_COUNT
*
LIVE_SEGMENT_DURATION_MS
-
liveEdgeLatencyMs
;
assertEquals
(
2000000L
,
((
MediaChunk
)
out
.
chunk
).
startTimeUs
);
long
chunkStartTimeMs
=
6000
;
assertEquals
(
3000000L
,
((
MediaChunk
)
out
.
chunk
).
endTimeUs
);
long
chunkEndTimeMs
=
7000
;
checkLiveEdgeLatencyWithTimeline
(
startTimeMs
,
liveEdgeLatencyMs
,
seekPositionMs
,
seekRangeStartMs
,
seekRangeEndMs
,
chunkStartTimeMs
,
chunkEndTimeMs
);
checkLiveEdgeLatencyWithTemplateAndUnlimitedTimeshift
(
startTimeMs
,
liveEdgeLatencyMs
,
seekPositionMs
,
seekRangeEndMs
,
chunkStartTimeMs
,
chunkEndTimeMs
);
checkLiveEdgeLatencyWithTemplateAndLimitedTimeshift
(
startTimeMs
,
liveEdgeLatencyMs
,
seekPositionMs
,
seekRangeStartMs
,
seekRangeEndMs
,
chunkStartTimeMs
,
chunkEndTimeMs
);
}
}
public
void
testLiveEdgeVeryHighLatencyWithTemplate
()
{
public
void
testLiveEdge2500msLatencyInProgress
()
{
DashChunkSource
chunkSource
=
setupLiveEdgeTemplateTest
(
10000L
);
long
startTimeMs
=
3000
;
List
<
MediaChunk
>
queue
=
new
ArrayList
<
MediaChunk
>();
long
liveEdgeLatencyMs
=
2500
;
ChunkOperationHolder
out
=
new
ChunkOperationHolder
();
long
seekPositionMs
=
LIVE_SEEK_BEYOND_EDGE_MS
;
chunkSource
.
getChunkOperation
(
queue
,
0
,
0
,
out
);
long
seekRangeStartMs
=
3000
;
long
seekRangeEndMs
=
3000
+
LIVE_SEGMENT_COUNT
*
LIVE_SEGMENT_DURATION_MS
-
liveEdgeLatencyMs
;
long
chunkStartTimeMs
=
5000
;
long
chunkEndTimeMs
=
6000
;
checkLiveEdgeLatencyWithTimeline
(
startTimeMs
,
liveEdgeLatencyMs
,
seekPositionMs
,
seekRangeStartMs
,
seekRangeEndMs
,
chunkStartTimeMs
,
chunkEndTimeMs
);
checkLiveEdgeLatencyWithTemplateAndUnlimitedTimeshift
(
startTimeMs
,
liveEdgeLatencyMs
,
seekPositionMs
,
seekRangeEndMs
,
chunkStartTimeMs
,
chunkEndTimeMs
);
checkLiveEdgeLatencyWithTemplateAndLimitedTimeshift
(
startTimeMs
,
liveEdgeLatencyMs
,
seekPositionMs
,
seekRangeStartMs
,
seekRangeEndMs
,
chunkStartTimeMs
,
chunkEndTimeMs
);
}
assertEquals
(
0L
,
((
MediaChunk
)
out
.
chunk
).
startTimeUs
);
public
void
testLiveEdgeVeryHighLatencyInProgress
()
{
assertEquals
(
1000000L
,
((
MediaChunk
)
out
.
chunk
).
endTimeUs
);
long
startTimeMs
=
3000
;
long
liveEdgeLatencyMs
=
10000
;
long
seekPositionMs
=
LIVE_SEEK_BEYOND_EDGE_MS
;
long
seekRangeStartMs
=
3000
;
long
seekRangeEndMs
=
3000
;
long
chunkStartTimeMs
=
3000
;
long
chunkEndTimeMs
=
4000
;
checkLiveEdgeLatencyWithTimeline
(
startTimeMs
,
liveEdgeLatencyMs
,
seekPositionMs
,
seekRangeStartMs
,
seekRangeEndMs
,
chunkStartTimeMs
,
chunkEndTimeMs
);
checkLiveEdgeLatencyWithTemplateAndUnlimitedTimeshift
(
startTimeMs
,
liveEdgeLatencyMs
,
seekPositionMs
,
0
,
0
,
1000
);
checkLiveEdgeLatencyWithTemplateAndLimitedTimeshift
(
startTimeMs
,
liveEdgeLatencyMs
,
seekPositionMs
,
seekRangeStartMs
,
seekRangeEndMs
,
chunkStartTimeMs
,
chunkEndTimeMs
);
}
}
private
static
MediaPresentationDescription
generateMpd
(
boolean
live
,
private
static
MediaPresentationDescription
generateMpd
(
boolean
live
,
List
<
Representation
>
representations
)
{
List
<
Representation
>
representations
,
boolean
limitTimeshiftBuffer
)
{
Representation
firstRepresentation
=
representations
.
get
(
0
);
Representation
firstRepresentation
=
representations
.
get
(
0
);
AdaptationSet
adaptationSet
=
new
AdaptationSet
(
0
,
AdaptationSet
.
TYPE_UNKNOWN
,
representations
);
AdaptationSet
adaptationSet
=
new
AdaptationSet
(
0
,
AdaptationSet
.
TYPE_UNKNOWN
,
representations
);
Period
period
=
new
Period
(
null
,
firstRepresentation
.
periodStartMs
,
Period
period
=
new
Period
(
null
,
firstRepresentation
.
periodStartMs
,
firstRepresentation
.
periodDurationMs
,
Collections
.
singletonList
(
adaptationSet
));
firstRepresentation
.
periodDurationMs
,
Collections
.
singletonList
(
adaptationSet
));
long
duration
=
(
live
)
?
TrackRenderer
.
UNKNOWN_TIME_US
long
duration
=
(
live
)
?
TrackRenderer
.
UNKNOWN_TIME_US
:
firstRepresentation
.
periodDurationMs
-
firstRepresentation
.
periodStartMs
;
:
firstRepresentation
.
periodDurationMs
-
firstRepresentation
.
periodStartMs
;
return
new
MediaPresentationDescription
(
AVAILABILITY_START_TIME
,
duration
,
-
1
,
live
,
-
1
,
-
1
,
return
new
MediaPresentationDescription
(
AVAILABILITY_START_TIME_MS
,
duration
,
-
1
,
live
,
-
1
,
(
limitTimeshiftBuffer
)
?
LIVE_TIMESHIFT_BUFFER_DEPTH_MS
:
-
1
,
null
,
Collections
.
singletonList
(
period
));
null
,
Collections
.
singletonList
(
period
));
}
}
...
@@ -256,72 +387,126 @@ public class DashChunkSourceTest extends InstrumentationTestCase {
...
@@ -256,72 +387,126 @@ public class DashChunkSourceTest extends InstrumentationTestCase {
SingleSegmentBase
segmentBase1
=
new
SingleSegmentBase
(
"https://example.com/1.mp4"
);
SingleSegmentBase
segmentBase1
=
new
SingleSegmentBase
(
"https://example.com/1.mp4"
);
Representation
representation1
=
Representation
representation1
=
Representation
.
newInstance
(
0
,
0
,
null
,
0
,
TALL_VIDEO
,
segmentBase1
);
Representation
.
newInstance
(
0
,
VOD_DURATION
,
null
,
0
,
TALL_VIDEO
,
segmentBase1
);
representations
.
add
(
representation1
);
representations
.
add
(
representation1
);
SingleSegmentBase
segmentBase2
=
new
SingleSegmentBase
(
"https://example.com/2.mp4"
);
SingleSegmentBase
segmentBase2
=
new
SingleSegmentBase
(
"https://example.com/2.mp4"
);
Representation
representation2
=
Representation
representation2
=
Representation
.
newInstance
(
0
,
0
,
null
,
0
,
WIDE_VIDEO
,
segmentBase2
);
Representation
.
newInstance
(
0
,
VOD_DURATION
,
null
,
0
,
WIDE_VIDEO
,
segmentBase2
);
representations
.
add
(
representation2
);
representations
.
add
(
representation2
);
return
generateMpd
(
false
,
representations
);
return
generateMpd
(
false
,
representations
,
false
);
}
}
private
static
MediaPresentationDescription
generateLiveMpdWithTimeline
()
{
private
static
MediaPresentationDescription
generateLiveMpdWithTimeline
(
long
startTime
)
{
List
<
Representation
>
representations
=
new
ArrayList
<
Representation
>();
List
<
Representation
>
representations
=
new
ArrayList
<
Representation
>();
List
<
SegmentTimelineElement
>
segmentTimeline
=
new
ArrayList
<
SegmentTimelineElement
>();
List
<
SegmentTimelineElement
>
segmentTimeline
=
new
ArrayList
<
SegmentTimelineElement
>();
segmentTimeline
.
add
(
new
SegmentTimelineElement
(
0L
,
1000L
));
segmentTimeline
.
add
(
new
SegmentTimelineElement
(
1000L
,
1000L
));
segmentTimeline
.
add
(
new
SegmentTimelineElement
(
2000L
,
1000L
));
segmentTimeline
.
add
(
new
SegmentTimelineElement
(
3000L
,
1000L
));
segmentTimeline
.
add
(
new
SegmentTimelineElement
(
4000L
,
1000L
));
List
<
RangedUri
>
mediaSegments
=
new
ArrayList
<
RangedUri
>();
List
<
RangedUri
>
mediaSegments
=
new
ArrayList
<
RangedUri
>();
mediaSegments
.
add
(
new
RangedUri
(
""
,
""
,
0L
,
500L
));
long
byteStart
=
0
;
mediaSegments
.
add
(
new
RangedUri
(
""
,
""
,
500L
,
500L
));
for
(
int
i
=
0
;
i
<
LIVE_SEGMENT_COUNT
;
i
++)
{
mediaSegments
.
add
(
new
RangedUri
(
""
,
""
,
1000L
,
500L
));
segmentTimeline
.
add
(
new
SegmentTimelineElement
(
startTime
,
LIVE_SEGMENT_DURATION_MS
));
mediaSegments
.
add
(
new
RangedUri
(
""
,
""
,
1500L
,
500L
));
mediaSegments
.
add
(
new
RangedUri
(
""
,
""
,
byteStart
,
500L
));
mediaSegments
.
add
(
new
RangedUri
(
""
,
""
,
2000L
,
500L
));
startTime
+=
LIVE_SEGMENT_DURATION_MS
;
byteStart
+=
500
;
}
MultiSegmentBase
segmentBase
=
new
SegmentList
(
null
,
1000
,
0
,
MultiSegmentBase
segmentBase
=
new
SegmentList
(
null
,
1000
,
0
,
TrackRenderer
.
UNKNOWN_TIME_US
,
1
,
TrackRenderer
.
UNKNOWN_TIME_US
,
segmentTimeline
,
TrackRenderer
.
UNKNOWN_TIME_US
,
0
,
TrackRenderer
.
UNKNOWN_TIME_US
,
segmentTimeline
,
mediaSegments
);
mediaSegments
);
Representation
representation
=
Representation
.
newInstance
(
0
,
TrackRenderer
.
UNKNOWN_TIME_US
,
Representation
representation
=
Representation
.
newInstance
(
startTime
,
null
,
0
,
REGULAR_VIDEO
,
segmentBase
);
TrackRenderer
.
UNKNOWN_TIME_US
,
null
,
0
,
REGULAR_VIDEO
,
segmentBase
);
representations
.
add
(
representation
);
representations
.
add
(
representation
);
return
generateMpd
(
true
,
representations
);
return
generateMpd
(
true
,
representations
,
false
);
}
}
private
static
MediaPresentationDescription
generateLiveMpdWithTemplate
()
{
private
static
MediaPresentationDescription
generateLiveMpdWithTemplate
(
boolean
limitTimeshiftBuffer
)
{
List
<
Representation
>
representations
=
new
ArrayList
<
Representation
>();
List
<
Representation
>
representations
=
new
ArrayList
<
Representation
>();
UrlTemplate
initializationTemplate
=
null
;
UrlTemplate
initializationTemplate
=
null
;
UrlTemplate
mediaTemplate
=
UrlTemplate
.
compile
(
"$RepresentationID$/$Number$"
);
UrlTemplate
mediaTemplate
=
UrlTemplate
.
compile
(
"$RepresentationID$/$Number$"
);
MultiSegmentBase
segmentBase
=
new
SegmentTemplate
(
null
,
1000
,
0
,
MultiSegmentBase
segmentBase
=
new
SegmentTemplate
(
null
,
1000
,
0
,
TrackRenderer
.
UNKNOWN_TIME_US
,
1
,
1000
,
null
,
TrackRenderer
.
UNKNOWN_TIME_US
,
0
,
LIVE_SEGMENT_DURATION_MS
,
null
,
initializationTemplate
,
mediaTemplate
,
"http://www.youtube.com"
);
initializationTemplate
,
mediaTemplate
,
"http://www.youtube.com"
);
Representation
representation
=
Representation
.
newInstance
(
0
,
TrackRenderer
.
UNKNOWN_TIME_US
,
Representation
representation
=
Representation
.
newInstance
(
0
,
TrackRenderer
.
UNKNOWN_TIME_US
,
null
,
0
,
REGULAR_VIDEO
,
segmentBase
);
null
,
0
,
REGULAR_VIDEO
,
segmentBase
);
representations
.
add
(
representation
);
representations
.
add
(
representation
);
return
generateMpd
(
true
,
representations
);
return
generateMpd
(
true
,
representations
,
limitTimeshiftBuffer
);
}
}
private
DashChunkSource
setupLiveEdgeTimelineTest
(
long
liveEdgeLatencyMs
)
{
private
DashChunkSource
setupLiveEdgeTimelineTest
(
long
startTime
,
long
liveEdgeLatencyMs
)
{
MediaPresentationDescription
manifest
=
generateLiveMpdWithTimeline
();
MediaPresentationDescription
manifest
=
generateLiveMpdWithTimeline
(
startTime
);
when
(
mockManifestFetcher
.
getManifest
()).
thenReturn
(
manifest
);
when
(
mockManifestFetcher
.
getManifest
()).
thenReturn
(
manifest
);
return
new
DashChunkSource
(
mockManifestFetcher
,
manifest
,
AdaptationSet
.
TYPE_VIDEO
,
null
,
DashChunkSource
chunkSource
=
new
DashChunkSource
(
mockManifestFetcher
,
manifest
,
mockDataSource
,
EVALUATOR
,
AVAILABILITY_CLOCK
,
liveEdgeLatencyMs
*
1000
,
AdaptationSet
.
TYPE_VIDEO
,
null
,
mockDataSource
,
EVALUATOR
,
AVAILABILITY_REALTIME_OFFSET
*
1000
);
new
FakeClock
(
AVAILABILITY_CURRENT_TIME_MS
+
startTime
),
liveEdgeLatencyMs
*
1000
,
AVAILABILITY_REALTIME_OFFSET_MS
*
1000
,
null
,
null
);
chunkSource
.
enable
();
return
chunkSource
;
}
}
private
DashChunkSource
setupLiveEdgeTemplateTest
(
long
liveEdgeLatencyMs
)
{
private
DashChunkSource
setupLiveEdgeTemplateTest
(
long
startTime
,
long
liveEdgeLatencyMs
,
MediaPresentationDescription
manifest
=
generateLiveMpdWithTemplate
();
boolean
limitTimeshiftBuffer
)
{
MediaPresentationDescription
manifest
=
generateLiveMpdWithTemplate
(
limitTimeshiftBuffer
);
when
(
mockManifestFetcher
.
getManifest
()).
thenReturn
(
manifest
);
when
(
mockManifestFetcher
.
getManifest
()).
thenReturn
(
manifest
);
return
new
DashChunkSource
(
mockManifestFetcher
,
manifest
,
AdaptationSet
.
TYPE_VIDEO
,
null
,
DashChunkSource
chunkSource
=
new
DashChunkSource
(
mockManifestFetcher
,
manifest
,
mockDataSource
,
EVALUATOR
,
AVAILABILITY_CLOCK
,
liveEdgeLatencyMs
*
1000
,
AdaptationSet
.
TYPE_VIDEO
,
null
,
mockDataSource
,
EVALUATOR
,
AVAILABILITY_REALTIME_OFFSET
*
1000
);
new
FakeClock
(
AVAILABILITY_CURRENT_TIME_MS
+
startTime
),
liveEdgeLatencyMs
*
1000
,
AVAILABILITY_REALTIME_OFFSET_MS
*
1000
,
null
,
null
);
chunkSource
.
enable
();
return
chunkSource
;
}
private
void
checkLiveEdgeLatencyWithTimeline
(
long
startTimeMs
,
long
liveEdgeLatencyMs
,
long
seekPositionMs
,
long
seekRangeStartMs
,
long
seekRangeEndMs
,
long
chunkStartTimeMs
,
long
chunkEndTimeMs
)
{
DashChunkSource
chunkSource
=
setupLiveEdgeTimelineTest
(
startTimeMs
,
liveEdgeLatencyMs
);
List
<
MediaChunk
>
queue
=
new
ArrayList
<
MediaChunk
>();
ChunkOperationHolder
out
=
new
ChunkOperationHolder
();
chunkSource
.
getChunkOperation
(
queue
,
seekPositionMs
*
1000
,
0
,
out
);
TimeRange
seekRange
=
chunkSource
.
getSeekRange
();
assertNotNull
(
out
.
chunk
);
long
[]
seekRangeValuesUs
=
seekRange
.
getCurrentBoundsUs
(
null
);
assertEquals
(
seekRangeStartMs
*
1000
,
seekRangeValuesUs
[
0
]);
assertEquals
(
seekRangeEndMs
*
1000
,
seekRangeValuesUs
[
1
]);
assertEquals
(
chunkStartTimeMs
*
1000
,
((
MediaChunk
)
out
.
chunk
).
startTimeUs
);
assertEquals
(
chunkEndTimeMs
*
1000
,
((
MediaChunk
)
out
.
chunk
).
endTimeUs
);
}
private
void
checkLiveEdgeLatencyWithTemplate
(
long
startTimeMs
,
long
liveEdgeLatencyMs
,
long
seekPositionMs
,
long
seekRangeStartMs
,
long
seekRangeEndMs
,
long
chunkStartTimeMs
,
long
chunkEndTimeMs
,
boolean
limitTimeshiftBuffer
)
{
DashChunkSource
chunkSource
=
setupLiveEdgeTemplateTest
(
startTimeMs
,
liveEdgeLatencyMs
,
limitTimeshiftBuffer
);
List
<
MediaChunk
>
queue
=
new
ArrayList
<
MediaChunk
>();
ChunkOperationHolder
out
=
new
ChunkOperationHolder
();
chunkSource
.
getChunkOperation
(
queue
,
seekPositionMs
*
1000
,
0
,
out
);
TimeRange
seekRange
=
chunkSource
.
getSeekRange
();
assertNotNull
(
out
.
chunk
);
long
[]
seekRangeValuesUs
=
seekRange
.
getCurrentBoundsUs
(
null
);
assertEquals
(
seekRangeStartMs
*
1000
,
seekRangeValuesUs
[
0
]);
assertEquals
(
seekRangeEndMs
*
1000
,
seekRangeValuesUs
[
1
]);
assertEquals
(
chunkStartTimeMs
*
1000
,
((
MediaChunk
)
out
.
chunk
).
startTimeUs
);
assertEquals
(
chunkEndTimeMs
*
1000
,
((
MediaChunk
)
out
.
chunk
).
endTimeUs
);
}
private
void
checkLiveEdgeLatencyWithTemplateAndUnlimitedTimeshift
(
long
startTimeMs
,
long
liveEdgeLatencyMs
,
long
seekPositionMs
,
long
seekRangeEndMs
,
long
chunkStartTimeMs
,
long
chunkEndTimeMs
)
{
checkLiveEdgeLatencyWithTemplate
(
startTimeMs
,
liveEdgeLatencyMs
,
seekPositionMs
,
0
,
seekRangeEndMs
,
chunkStartTimeMs
,
chunkEndTimeMs
,
false
);
}
private
void
checkLiveEdgeLatencyWithTemplateAndLimitedTimeshift
(
long
startTimeMs
,
long
liveEdgeLatencyMs
,
long
seekPositionMs
,
long
seekRangeStartMs
,
long
seekRangeEndMs
,
long
chunkStartTimeMs
,
long
chunkEndTimeMs
)
{
checkLiveEdgeLatencyWithTemplate
(
startTimeMs
,
liveEdgeLatencyMs
,
seekPositionMs
,
seekRangeStartMs
,
seekRangeEndMs
,
chunkStartTimeMs
,
chunkEndTimeMs
,
true
);
}
}
}
}
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