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
4228f2cf
authored
Jul 18, 2014
by
ojw28
Browse files
Options
_('Browse Files')
Download
Plain Diff
Merge pull request #15 from google/dev
Merge 1.0.11 to master
parents
273fad84
1ed65dfb
Show whitespace changes
Inline
Side-by-side
Showing
51 changed files
with
2735 additions
and
1522 deletions
README.md
demo/build.gradle
demo/src/main/java/com/google/android/exoplayer/demo/SampleChooserActivity.java
demo/src/main/java/com/google/android/exoplayer/demo/full/EventLogger.java
demo/src/main/java/com/google/android/exoplayer/demo/full/player/DashVodRendererBuilder.java
demo/src/main/java/com/google/android/exoplayer/demo/full/player/DemoPlayer.java
demo/src/main/java/com/google/android/exoplayer/demo/full/player/SmoothStreamingRendererBuilder.java
demo/src/main/java/com/google/android/exoplayer/demo/simple/DashVodRendererBuilder.java
demo/src/main/java/com/google/android/exoplayer/demo/simple/SmoothStreamingRendererBuilder.java
library/src/main/java/com/google/android/exoplayer/ExoPlayerLibraryInfo.java
library/src/main/java/com/google/android/exoplayer/MediaCodecAudioTrackRenderer.java
library/src/main/java/com/google/android/exoplayer/MediaCodecTrackRenderer.java
library/src/main/java/com/google/android/exoplayer/MediaCodecVideoTrackRenderer.java
library/src/main/java/com/google/android/exoplayer/SampleSource.java
library/src/main/java/com/google/android/exoplayer/chunk/ChunkSampleSource.java
library/src/main/java/com/google/android/exoplayer/chunk/ChunkSource.java
library/src/main/java/com/google/android/exoplayer/chunk/Format.java
library/src/main/java/com/google/android/exoplayer/chunk/FormatEvaluator.java
library/src/main/java/com/google/android/exoplayer/chunk/MediaChunk.java
library/src/main/java/com/google/android/exoplayer/chunk/Mp4MediaChunk.java
library/src/main/java/com/google/android/exoplayer/chunk/MultiTrackChunkSource.java
library/src/main/java/com/google/android/exoplayer/chunk/SingleSampleMediaChunk.java
library/src/main/java/com/google/android/exoplayer/chunk/WebmMediaChunk.java
library/src/main/java/com/google/android/exoplayer/dash/DashMp4ChunkSource.java
library/src/main/java/com/google/android/exoplayer/dash/DashWebmChunkSource.java
library/src/main/java/com/google/android/exoplayer/dash/mpd/MediaPresentationDescriptionFetcher.java
library/src/main/java/com/google/android/exoplayer/dash/mpd/MediaPresentationDescriptionParser.java
library/src/main/java/com/google/android/exoplayer/dash/mpd/Period.java
library/src/main/java/com/google/android/exoplayer/dash/mpd/RangedUri.java
library/src/main/java/com/google/android/exoplayer/dash/mpd/Representation.java
library/src/main/java/com/google/android/exoplayer/dash/mpd/Segment.java
library/src/main/java/com/google/android/exoplayer/dash/mpd/SegmentedRepresentation.java
library/src/main/java/com/google/android/exoplayer/dash/mpd/UrlTemplate.java
library/src/main/java/com/google/android/exoplayer/parser/mp4/Atom.java
library/src/main/java/com/google/android/exoplayer/parser/mp4/FragmentedMp4Extractor.java
library/src/main/java/com/google/android/exoplayer/parser/webm/DefaultEbmlReader.java
library/src/main/java/com/google/android/exoplayer/parser/webm/DefaultWebmExtractor.java
library/src/main/java/com/google/android/exoplayer/parser/webm/EbmlEventHandler.java
library/src/main/java/com/google/android/exoplayer/parser/webm/EbmlReader.java
library/src/main/java/com/google/android/exoplayer/parser/webm/WebmExtractor.java
library/src/main/java/com/google/android/exoplayer/smoothstreaming/SmoothStreamingChunkSource.java
library/src/main/java/com/google/android/exoplayer/smoothstreaming/SmoothStreamingManifest.java
library/src/main/java/com/google/android/exoplayer/smoothstreaming/SmoothStreamingManifestFetcher.java
library/src/main/java/com/google/android/exoplayer/text/ttml/TtmlSubtitle.java
library/src/main/java/com/google/android/exoplayer/upstream/DataSourceStream.java
library/src/main/java/com/google/android/exoplayer/upstream/DefaultBandwidthMeter.java
library/src/main/java/com/google/android/exoplayer/upstream/cache/Cache.java
library/src/main/java/com/google/android/exoplayer/upstream/cache/SimpleCache.java
library/src/main/java/com/google/android/exoplayer/util/ManifestFetcher.java
library/src/main/java/com/google/android/exoplayer/util/MimeTypes.java
library/src/main/java/com/google/android/exoplayer/util/Util.java
README.md
View file @
4228f2cf
...
@@ -26,6 +26,16 @@ get started.
...
@@ -26,6 +26,16 @@ get started.
[
Class reference
]:
http://google.github.io/ExoPlayer/doc/reference/com/google/android/exoplayer/package-summary.html
[
Class reference
]:
http://google.github.io/ExoPlayer/doc/reference/com/google/android/exoplayer/package-summary.html
## Project branches ##
*
The
[
master
][]
branch holds the most recent minor release.
*
Most development work happens on the
[
dev
][]
branch.
*
Additional development branches may be established for major features.
[
master
]:
https://github.com/google/ExoPlayer/tree/master
[
dev
]:
https://github.com/google/ExoPlayer/tree/dev
## Using Eclipse ##
## Using Eclipse ##
The repository includes Eclipse projects for both the ExoPlayer library and its
The repository includes Eclipse projects for both the ExoPlayer library and its
...
...
demo/build.gradle
View file @
4228f2cf
...
@@ -18,7 +18,7 @@ android {
...
@@ -18,7 +18,7 @@ android {
buildToolsVersion
"19.1"
buildToolsVersion
"19.1"
defaultConfig
{
defaultConfig
{
minSdkVersion
9
minSdkVersion
16
targetSdkVersion
19
targetSdkVersion
19
}
}
buildTypes
{
buildTypes
{
...
...
demo/src/main/java/com/google/android/exoplayer/demo/SampleChooserActivity.java
View file @
4228f2cf
...
@@ -52,7 +52,7 @@ public class SampleChooserActivity extends Activity {
...
@@ -52,7 +52,7 @@ public class SampleChooserActivity extends Activity {
sampleAdapter
.
addAll
((
Object
[])
Samples
.
SIMPLE
);
sampleAdapter
.
addAll
((
Object
[])
Samples
.
SIMPLE
);
sampleAdapter
.
add
(
new
Header
(
"YouTube DASH"
));
sampleAdapter
.
add
(
new
Header
(
"YouTube DASH"
));
sampleAdapter
.
addAll
((
Object
[])
Samples
.
YOUTUBE_DASH_MP4
);
sampleAdapter
.
addAll
((
Object
[])
Samples
.
YOUTUBE_DASH_MP4
);
sampleAdapter
.
add
(
new
Header
(
"Widevine
DASH GTS
"
));
sampleAdapter
.
add
(
new
Header
(
"Widevine
GTS DASH
"
));
sampleAdapter
.
addAll
((
Object
[])
Samples
.
WIDEVINE_GTS
);
sampleAdapter
.
addAll
((
Object
[])
Samples
.
WIDEVINE_GTS
);
sampleAdapter
.
add
(
new
Header
(
"SmoothStreaming"
));
sampleAdapter
.
add
(
new
Header
(
"SmoothStreaming"
));
sampleAdapter
.
addAll
((
Object
[])
Samples
.
SMOOTHSTREAMING
);
sampleAdapter
.
addAll
((
Object
[])
Samples
.
SMOOTHSTREAMING
);
...
...
demo/src/main/java/com/google/android/exoplayer/demo/full/EventLogger.java
View file @
4228f2cf
...
@@ -91,7 +91,7 @@ public class EventLogger implements DemoPlayer.Listener, DemoPlayer.InfoListener
...
@@ -91,7 +91,7 @@ public class EventLogger implements DemoPlayer.Listener, DemoPlayer.InfoListener
}
}
@Override
@Override
public
void
onLoadStarted
(
int
sourceId
,
int
formatId
,
int
trigger
,
boolean
isInitialization
,
public
void
onLoadStarted
(
int
sourceId
,
String
formatId
,
int
trigger
,
boolean
isInitialization
,
int
mediaStartTimeMs
,
int
mediaEndTimeMs
,
long
totalBytes
)
{
int
mediaStartTimeMs
,
int
mediaEndTimeMs
,
long
totalBytes
)
{
loadStartTimeMs
[
sourceId
]
=
SystemClock
.
elapsedRealtime
();
loadStartTimeMs
[
sourceId
]
=
SystemClock
.
elapsedRealtime
();
if
(
VerboseLogUtil
.
isTagEnabled
(
TAG
))
{
if
(
VerboseLogUtil
.
isTagEnabled
(
TAG
))
{
...
@@ -110,13 +110,13 @@ public class EventLogger implements DemoPlayer.Listener, DemoPlayer.InfoListener
...
@@ -110,13 +110,13 @@ public class EventLogger implements DemoPlayer.Listener, DemoPlayer.InfoListener
}
}
@Override
@Override
public
void
onVideoFormatEnabled
(
int
formatId
,
int
trigger
,
int
mediaTimeMs
)
{
public
void
onVideoFormatEnabled
(
String
formatId
,
int
trigger
,
int
mediaTimeMs
)
{
Log
.
d
(
TAG
,
"videoFormat ["
+
getSessionTimeString
()
+
", "
+
formatId
+
", "
+
Log
.
d
(
TAG
,
"videoFormat ["
+
getSessionTimeString
()
+
", "
+
formatId
+
", "
+
Integer
.
toString
(
trigger
)
+
"]"
);
Integer
.
toString
(
trigger
)
+
"]"
);
}
}
@Override
@Override
public
void
onAudioFormatEnabled
(
int
formatId
,
int
trigger
,
int
mediaTimeMs
)
{
public
void
onAudioFormatEnabled
(
String
formatId
,
int
trigger
,
int
mediaTimeMs
)
{
Log
.
d
(
TAG
,
"audioFormat ["
+
getSessionTimeString
()
+
", "
+
formatId
+
", "
+
Log
.
d
(
TAG
,
"audioFormat ["
+
getSessionTimeString
()
+
", "
+
formatId
+
", "
+
Integer
.
toString
(
trigger
)
+
"]"
);
Integer
.
toString
(
trigger
)
+
"]"
);
}
}
...
...
demo/src/main/java/com/google/android/exoplayer/demo/full/player/DashVodRendererBuilder.java
View file @
4228f2cf
...
@@ -160,8 +160,7 @@ public class DashVodRendererBuilder implements RendererBuilder,
...
@@ -160,8 +160,7 @@ public class DashVodRendererBuilder implements RendererBuilder,
}
}
// Build the video renderer.
// Build the video renderer.
DataSource
videoDataSource
=
new
HttpDataSource
(
userAgent
,
HttpDataSource
.
REJECT_PAYWALL_TYPES
,
DataSource
videoDataSource
=
new
HttpDataSource
(
userAgent
,
null
,
bandwidthMeter
);
bandwidthMeter
);
ChunkSource
videoChunkSource
;
ChunkSource
videoChunkSource
;
String
mimeType
=
videoRepresentations
[
0
].
format
.
mimeType
;
String
mimeType
=
videoRepresentations
[
0
].
format
.
mimeType
;
if
(
mimeType
.
equals
(
MimeTypes
.
VIDEO_MP4
))
{
if
(
mimeType
.
equals
(
MimeTypes
.
VIDEO_MP4
))
{
...
@@ -192,8 +191,7 @@ public class DashVodRendererBuilder implements RendererBuilder,
...
@@ -192,8 +191,7 @@ public class DashVodRendererBuilder implements RendererBuilder,
audioChunkSource
=
null
;
audioChunkSource
=
null
;
audioRenderer
=
null
;
audioRenderer
=
null
;
}
else
{
}
else
{
DataSource
audioDataSource
=
new
HttpDataSource
(
userAgent
,
DataSource
audioDataSource
=
new
HttpDataSource
(
userAgent
,
null
,
bandwidthMeter
);
HttpDataSource
.
REJECT_PAYWALL_TYPES
,
bandwidthMeter
);
audioTrackNames
=
new
String
[
audioRepresentationsList
.
size
()];
audioTrackNames
=
new
String
[
audioRepresentationsList
.
size
()];
ChunkSource
[]
audioChunkSources
=
new
ChunkSource
[
audioRepresentationsList
.
size
()];
ChunkSource
[]
audioChunkSources
=
new
ChunkSource
[
audioRepresentationsList
.
size
()];
FormatEvaluator
audioEvaluator
=
new
FormatEvaluator
.
FixedEvaluator
();
FormatEvaluator
audioEvaluator
=
new
FormatEvaluator
.
FixedEvaluator
();
...
...
demo/src/main/java/com/google/android/exoplayer/demo/full/player/DemoPlayer.java
View file @
4228f2cf
...
@@ -118,11 +118,11 @@ public class DemoPlayer implements ExoPlayer.Listener, ChunkSampleSource.EventLi
...
@@ -118,11 +118,11 @@ public class DemoPlayer implements ExoPlayer.Listener, ChunkSampleSource.EventLi
* A listener for debugging information.
* A listener for debugging information.
*/
*/
public
interface
InfoListener
{
public
interface
InfoListener
{
void
onVideoFormatEnabled
(
int
formatId
,
int
trigger
,
int
mediaTimeMs
);
void
onVideoFormatEnabled
(
String
formatId
,
int
trigger
,
int
mediaTimeMs
);
void
onAudioFormatEnabled
(
int
formatId
,
int
trigger
,
int
mediaTimeMs
);
void
onAudioFormatEnabled
(
String
formatId
,
int
trigger
,
int
mediaTimeMs
);
void
onDroppedFrames
(
int
count
,
long
elapsed
);
void
onDroppedFrames
(
int
count
,
long
elapsed
);
void
onBandwidthSample
(
int
elapsedMs
,
long
bytes
,
long
bandwidthEstimate
);
void
onBandwidthSample
(
int
elapsedMs
,
long
bytes
,
long
bandwidthEstimate
);
void
onLoadStarted
(
int
sourceId
,
int
formatId
,
int
trigger
,
boolean
isInitialization
,
void
onLoadStarted
(
int
sourceId
,
String
formatId
,
int
trigger
,
boolean
isInitialization
,
int
mediaStartTimeMs
,
int
mediaEndTimeMs
,
long
totalBytes
);
int
mediaStartTimeMs
,
int
mediaEndTimeMs
,
long
totalBytes
);
void
onLoadCompleted
(
int
sourceId
);
void
onLoadCompleted
(
int
sourceId
);
}
}
...
@@ -398,7 +398,8 @@ public class DemoPlayer implements ExoPlayer.Listener, ChunkSampleSource.EventLi
...
@@ -398,7 +398,8 @@ public class DemoPlayer implements ExoPlayer.Listener, ChunkSampleSource.EventLi
}
}
@Override
@Override
public
void
onDownstreamFormatChanged
(
int
sourceId
,
int
formatId
,
int
trigger
,
int
mediaTimeMs
)
{
public
void
onDownstreamFormatChanged
(
int
sourceId
,
String
formatId
,
int
trigger
,
int
mediaTimeMs
)
{
if
(
infoListener
==
null
)
{
if
(
infoListener
==
null
)
{
return
;
return
;
}
}
...
@@ -469,7 +470,7 @@ public class DemoPlayer implements ExoPlayer.Listener, ChunkSampleSource.EventLi
...
@@ -469,7 +470,7 @@ public class DemoPlayer implements ExoPlayer.Listener, ChunkSampleSource.EventLi
}
}
@Override
@Override
public
void
onLoadStarted
(
int
sourceId
,
int
formatId
,
int
trigger
,
boolean
isInitialization
,
public
void
onLoadStarted
(
int
sourceId
,
String
formatId
,
int
trigger
,
boolean
isInitialization
,
int
mediaStartTimeMs
,
int
mediaEndTimeMs
,
long
totalBytes
)
{
int
mediaStartTimeMs
,
int
mediaEndTimeMs
,
long
totalBytes
)
{
if
(
infoListener
!=
null
)
{
if
(
infoListener
!=
null
)
{
infoListener
.
onLoadStarted
(
sourceId
,
formatId
,
trigger
,
isInitialization
,
mediaStartTimeMs
,
infoListener
.
onLoadStarted
(
sourceId
,
formatId
,
trigger
,
isInitialization
,
mediaStartTimeMs
,
...
...
demo/src/main/java/com/google/android/exoplayer/demo/full/player/SmoothStreamingRendererBuilder.java
View file @
4228f2cf
...
@@ -150,8 +150,7 @@ public class SmoothStreamingRendererBuilder implements RendererBuilder,
...
@@ -150,8 +150,7 @@ public class SmoothStreamingRendererBuilder implements RendererBuilder,
}
}
// Build the video renderer.
// Build the video renderer.
DataSource
videoDataSource
=
new
HttpDataSource
(
userAgent
,
HttpDataSource
.
REJECT_PAYWALL_TYPES
,
DataSource
videoDataSource
=
new
HttpDataSource
(
userAgent
,
null
,
bandwidthMeter
);
bandwidthMeter
);
ChunkSource
videoChunkSource
=
new
SmoothStreamingChunkSource
(
url
,
manifest
,
ChunkSource
videoChunkSource
=
new
SmoothStreamingChunkSource
(
url
,
manifest
,
videoStreamElementIndex
,
videoTrackIndices
,
videoDataSource
,
videoStreamElementIndex
,
videoTrackIndices
,
videoDataSource
,
new
AdaptiveEvaluator
(
bandwidthMeter
));
new
AdaptiveEvaluator
(
bandwidthMeter
));
...
@@ -173,8 +172,7 @@ public class SmoothStreamingRendererBuilder implements RendererBuilder,
...
@@ -173,8 +172,7 @@ public class SmoothStreamingRendererBuilder implements RendererBuilder,
}
else
{
}
else
{
audioTrackNames
=
new
String
[
audioStreamElementCount
];
audioTrackNames
=
new
String
[
audioStreamElementCount
];
ChunkSource
[]
audioChunkSources
=
new
ChunkSource
[
audioStreamElementCount
];
ChunkSource
[]
audioChunkSources
=
new
ChunkSource
[
audioStreamElementCount
];
DataSource
audioDataSource
=
new
HttpDataSource
(
userAgent
,
DataSource
audioDataSource
=
new
HttpDataSource
(
userAgent
,
null
,
bandwidthMeter
);
HttpDataSource
.
REJECT_PAYWALL_TYPES
,
bandwidthMeter
);
FormatEvaluator
audioFormatEvaluator
=
new
FormatEvaluator
.
FixedEvaluator
();
FormatEvaluator
audioFormatEvaluator
=
new
FormatEvaluator
.
FixedEvaluator
();
audioStreamElementCount
=
0
;
audioStreamElementCount
=
0
;
for
(
int
i
=
0
;
i
<
manifest
.
streamElements
.
length
;
i
++)
{
for
(
int
i
=
0
;
i
<
manifest
.
streamElements
.
length
;
i
++)
{
...
@@ -204,8 +202,7 @@ public class SmoothStreamingRendererBuilder implements RendererBuilder,
...
@@ -204,8 +202,7 @@ public class SmoothStreamingRendererBuilder implements RendererBuilder,
}
else
{
}
else
{
textTrackNames
=
new
String
[
textStreamElementCount
];
textTrackNames
=
new
String
[
textStreamElementCount
];
ChunkSource
[]
textChunkSources
=
new
ChunkSource
[
textStreamElementCount
];
ChunkSource
[]
textChunkSources
=
new
ChunkSource
[
textStreamElementCount
];
DataSource
ttmlDataSource
=
new
HttpDataSource
(
userAgent
,
HttpDataSource
.
REJECT_PAYWALL_TYPES
,
DataSource
ttmlDataSource
=
new
HttpDataSource
(
userAgent
,
null
,
bandwidthMeter
);
bandwidthMeter
);
FormatEvaluator
ttmlFormatEvaluator
=
new
FormatEvaluator
.
FixedEvaluator
();
FormatEvaluator
ttmlFormatEvaluator
=
new
FormatEvaluator
.
FixedEvaluator
();
textStreamElementCount
=
0
;
textStreamElementCount
=
0
;
for
(
int
i
=
0
;
i
<
manifest
.
streamElements
.
length
;
i
++)
{
for
(
int
i
=
0
;
i
<
manifest
.
streamElements
.
length
;
i
++)
{
...
...
demo/src/main/java/com/google/android/exoplayer/demo/simple/DashVodRendererBuilder.java
View file @
4228f2cf
...
@@ -115,8 +115,7 @@ import java.util.ArrayList;
...
@@ -115,8 +115,7 @@ import java.util.ArrayList;
videoRepresentationsList
.
toArray
(
videoRepresentations
);
videoRepresentationsList
.
toArray
(
videoRepresentations
);
// Build the video renderer.
// Build the video renderer.
DataSource
videoDataSource
=
new
HttpDataSource
(
userAgent
,
HttpDataSource
.
REJECT_PAYWALL_TYPES
,
DataSource
videoDataSource
=
new
HttpDataSource
(
userAgent
,
null
,
bandwidthMeter
);
bandwidthMeter
);
ChunkSource
videoChunkSource
=
new
DashMp4ChunkSource
(
videoDataSource
,
ChunkSource
videoChunkSource
=
new
DashMp4ChunkSource
(
videoDataSource
,
new
AdaptiveEvaluator
(
bandwidthMeter
),
videoRepresentations
);
new
AdaptiveEvaluator
(
bandwidthMeter
),
videoRepresentations
);
ChunkSampleSource
videoSampleSource
=
new
ChunkSampleSource
(
videoChunkSource
,
loadControl
,
ChunkSampleSource
videoSampleSource
=
new
ChunkSampleSource
(
videoChunkSource
,
loadControl
,
...
@@ -125,8 +124,7 @@ import java.util.ArrayList;
...
@@ -125,8 +124,7 @@ import java.util.ArrayList;
MediaCodec
.
VIDEO_SCALING_MODE_SCALE_TO_FIT
,
0
,
mainHandler
,
playerActivity
,
50
);
MediaCodec
.
VIDEO_SCALING_MODE_SCALE_TO_FIT
,
0
,
mainHandler
,
playerActivity
,
50
);
// Build the audio renderer.
// Build the audio renderer.
DataSource
audioDataSource
=
new
HttpDataSource
(
userAgent
,
HttpDataSource
.
REJECT_PAYWALL_TYPES
,
DataSource
audioDataSource
=
new
HttpDataSource
(
userAgent
,
null
,
bandwidthMeter
);
bandwidthMeter
);
ChunkSource
audioChunkSource
=
new
DashMp4ChunkSource
(
audioDataSource
,
ChunkSource
audioChunkSource
=
new
DashMp4ChunkSource
(
audioDataSource
,
new
FormatEvaluator
.
FixedEvaluator
(),
audioRepresentation
);
new
FormatEvaluator
.
FixedEvaluator
(),
audioRepresentation
);
SampleSource
audioSampleSource
=
new
ChunkSampleSource
(
audioChunkSource
,
loadControl
,
SampleSource
audioSampleSource
=
new
ChunkSampleSource
(
audioChunkSource
,
loadControl
,
...
...
demo/src/main/java/com/google/android/exoplayer/demo/simple/SmoothStreamingRendererBuilder.java
View file @
4228f2cf
...
@@ -115,8 +115,7 @@ import java.util.ArrayList;
...
@@ -115,8 +115,7 @@ import java.util.ArrayList;
}
}
// Build the video renderer.
// Build the video renderer.
DataSource
videoDataSource
=
new
HttpDataSource
(
userAgent
,
HttpDataSource
.
REJECT_PAYWALL_TYPES
,
DataSource
videoDataSource
=
new
HttpDataSource
(
userAgent
,
null
,
bandwidthMeter
);
bandwidthMeter
);
ChunkSource
videoChunkSource
=
new
SmoothStreamingChunkSource
(
url
,
manifest
,
ChunkSource
videoChunkSource
=
new
SmoothStreamingChunkSource
(
url
,
manifest
,
videoStreamElementIndex
,
videoTrackIndices
,
videoDataSource
,
videoStreamElementIndex
,
videoTrackIndices
,
videoDataSource
,
new
AdaptiveEvaluator
(
bandwidthMeter
));
new
AdaptiveEvaluator
(
bandwidthMeter
));
...
@@ -126,8 +125,7 @@ import java.util.ArrayList;
...
@@ -126,8 +125,7 @@ import java.util.ArrayList;
MediaCodec
.
VIDEO_SCALING_MODE_SCALE_TO_FIT
,
0
,
mainHandler
,
playerActivity
,
50
);
MediaCodec
.
VIDEO_SCALING_MODE_SCALE_TO_FIT
,
0
,
mainHandler
,
playerActivity
,
50
);
// Build the audio renderer.
// Build the audio renderer.
DataSource
audioDataSource
=
new
HttpDataSource
(
userAgent
,
HttpDataSource
.
REJECT_PAYWALL_TYPES
,
DataSource
audioDataSource
=
new
HttpDataSource
(
userAgent
,
null
,
bandwidthMeter
);
bandwidthMeter
);
ChunkSource
audioChunkSource
=
new
SmoothStreamingChunkSource
(
url
,
manifest
,
ChunkSource
audioChunkSource
=
new
SmoothStreamingChunkSource
(
url
,
manifest
,
audioStreamElementIndex
,
new
int
[]
{
0
},
audioDataSource
,
audioStreamElementIndex
,
new
int
[]
{
0
},
audioDataSource
,
new
FormatEvaluator
.
FixedEvaluator
());
new
FormatEvaluator
.
FixedEvaluator
());
...
...
library/src/main/java/com/google/android/exoplayer/ExoPlayerLibraryInfo.java
View file @
4228f2cf
...
@@ -26,7 +26,7 @@ public class ExoPlayerLibraryInfo {
...
@@ -26,7 +26,7 @@ public class ExoPlayerLibraryInfo {
/**
/**
* The version of the library, expressed as a string.
* The version of the library, expressed as a string.
*/
*/
public
static
final
String
VERSION
=
"1.0.1
0
"
;
public
static
final
String
VERSION
=
"1.0.1
1
"
;
/**
/**
* The version of the library, expressed as an integer.
* The version of the library, expressed as an integer.
...
...
library/src/main/java/com/google/android/exoplayer/MediaCodecAudioTrackRenderer.java
View file @
4228f2cf
...
@@ -95,6 +95,10 @@ public class MediaCodecAudioTrackRenderer extends MediaCodecTrackRenderer {
...
@@ -95,6 +95,10 @@ public class MediaCodecAudioTrackRenderer extends MediaCodecTrackRenderer {
private
static
final
int
MIN_PLAYHEAD_OFFSET_SAMPLE_INTERVAL_US
=
30000
;
private
static
final
int
MIN_PLAYHEAD_OFFSET_SAMPLE_INTERVAL_US
=
30000
;
private
static
final
int
MIN_TIMESTAMP_SAMPLE_INTERVAL_US
=
500000
;
private
static
final
int
MIN_TIMESTAMP_SAMPLE_INTERVAL_US
=
500000
;
private
static
final
int
START_NOT_SET
=
0
;
private
static
final
int
START_IN_SYNC
=
1
;
private
static
final
int
START_NEED_SYNC
=
2
;
private
final
EventListener
eventListener
;
private
final
EventListener
eventListener
;
private
final
ConditionVariable
audioTrackReleasingConditionVariable
;
private
final
ConditionVariable
audioTrackReleasingConditionVariable
;
private
final
AudioTimestampCompat
audioTimestampCompat
;
private
final
AudioTimestampCompat
audioTimestampCompat
;
...
@@ -119,7 +123,7 @@ public class MediaCodecAudioTrackRenderer extends MediaCodecTrackRenderer {
...
@@ -119,7 +123,7 @@ public class MediaCodecAudioTrackRenderer extends MediaCodecTrackRenderer {
private
Method
audioTrackGetLatencyMethod
;
private
Method
audioTrackGetLatencyMethod
;
private
int
audioSessionId
;
private
int
audioSessionId
;
private
long
submittedBytes
;
private
long
submittedBytes
;
private
boolean
audioTrackStartMediaTimeSet
;
private
int
audioTrackStartMediaTimeState
;
private
long
audioTrackStartMediaTimeUs
;
private
long
audioTrackStartMediaTimeUs
;
private
long
audioTrackResumeSystemTimeUs
;
private
long
audioTrackResumeSystemTimeUs
;
private
long
lastReportedCurrentPositionUs
;
private
long
lastReportedCurrentPositionUs
;
...
@@ -363,7 +367,7 @@ public class MediaCodecAudioTrackRenderer extends MediaCodecTrackRenderer {
...
@@ -363,7 +367,7 @@ public class MediaCodecAudioTrackRenderer extends MediaCodecTrackRenderer {
lastRawPlaybackHeadPosition
=
0
;
lastRawPlaybackHeadPosition
=
0
;
rawPlaybackHeadWrapCount
=
0
;
rawPlaybackHeadWrapCount
=
0
;
audioTrackStartMediaTimeUs
=
0
;
audioTrackStartMediaTimeUs
=
0
;
audioTrackStartMediaTimeS
et
=
false
;
audioTrackStartMediaTimeS
tate
=
START_NOT_SET
;
resetSyncParams
();
resetSyncParams
();
int
playState
=
audioTrack
.
getPlayState
();
int
playState
=
audioTrack
.
getPlayState
();
if
(
playState
==
AudioTrack
.
PLAYSTATE_PLAYING
)
{
if
(
playState
==
AudioTrack
.
PLAYSTATE_PLAYING
)
{
...
@@ -429,7 +433,7 @@ public class MediaCodecAudioTrackRenderer extends MediaCodecTrackRenderer {
...
@@ -429,7 +433,7 @@ public class MediaCodecAudioTrackRenderer extends MediaCodecTrackRenderer {
protected
long
getCurrentPositionUs
()
{
protected
long
getCurrentPositionUs
()
{
long
systemClockUs
=
System
.
nanoTime
()
/
1000
;
long
systemClockUs
=
System
.
nanoTime
()
/
1000
;
long
currentPositionUs
;
long
currentPositionUs
;
if
(
audioTrack
==
null
||
!
audioTrackStartMediaTimeSet
)
{
if
(
audioTrack
==
null
||
audioTrackStartMediaTimeState
==
START_NOT_SET
)
{
// The AudioTrack hasn't started.
// The AudioTrack hasn't started.
currentPositionUs
=
super
.
getCurrentPositionUs
();
currentPositionUs
=
super
.
getCurrentPositionUs
();
}
else
if
(
audioTimestampSet
)
{
}
else
if
(
audioTimestampSet
)
{
...
@@ -461,7 +465,8 @@ public class MediaCodecAudioTrackRenderer extends MediaCodecTrackRenderer {
...
@@ -461,7 +465,8 @@ public class MediaCodecAudioTrackRenderer extends MediaCodecTrackRenderer {
}
}
private
void
maybeSampleSyncParams
()
{
private
void
maybeSampleSyncParams
()
{
if
(
audioTrack
==
null
||
!
audioTrackStartMediaTimeSet
||
getState
()
!=
STATE_STARTED
)
{
if
(
audioTrack
==
null
||
audioTrackStartMediaTimeState
==
START_NOT_SET
||
getState
()
!=
STATE_STARTED
)
{
// The AudioTrack isn't playing.
// The AudioTrack isn't playing.
return
;
return
;
}
}
...
@@ -549,25 +554,40 @@ public class MediaCodecAudioTrackRenderer extends MediaCodecTrackRenderer {
...
@@ -549,25 +554,40 @@ public class MediaCodecAudioTrackRenderer extends MediaCodecTrackRenderer {
@Override
@Override
protected
boolean
processOutputBuffer
(
long
timeUs
,
MediaCodec
codec
,
ByteBuffer
buffer
,
protected
boolean
processOutputBuffer
(
long
timeUs
,
MediaCodec
codec
,
ByteBuffer
buffer
,
MediaCodec
.
BufferInfo
bufferInfo
,
int
bufferIndex
)
throws
ExoPlaybackException
{
MediaCodec
.
BufferInfo
bufferInfo
,
int
bufferIndex
,
boolean
shouldSkip
)
throws
ExoPlaybackException
{
if
(
shouldSkip
)
{
codec
.
releaseOutputBuffer
(
bufferIndex
,
false
);
codecCounters
.
skippedOutputBufferCount
++;
if
(
audioTrackStartMediaTimeState
==
START_IN_SYNC
)
{
// Skipping the sample will push track time out of sync. We'll need to sync again.
audioTrackStartMediaTimeState
=
START_NEED_SYNC
;
}
return
true
;
}
if
(
temporaryBufferSize
==
0
)
{
if
(
temporaryBufferSize
==
0
)
{
// This is the first time we've seen this {@code buffer}.
// This is the first time we've seen this {@code buffer}.
// Note: presentationTimeUs corresponds to the end of the sample, not the start.
// Note: presentationTimeUs corresponds to the end of the sample, not the start.
long
bufferStartTime
=
bufferInfo
.
presentationTimeUs
-
long
bufferStartTime
=
bufferInfo
.
presentationTimeUs
-
framesToDurationUs
(
bufferInfo
.
size
/
frameSize
);
framesToDurationUs
(
bufferInfo
.
size
/
frameSize
);
if
(
!
audioTrackStartMediaTimeSet
)
{
if
(
audioTrackStartMediaTimeState
==
START_NOT_SET
)
{
audioTrackStartMediaTimeUs
=
Math
.
max
(
0
,
bufferStartTime
);
audioTrackStartMediaTimeUs
=
Math
.
max
(
0
,
bufferStartTime
);
audioTrackStartMediaTimeS
et
=
true
;
audioTrackStartMediaTimeS
tate
=
START_IN_SYNC
;
}
else
{
}
else
{
// Sanity check that bufferStartTime is consistent with the expected value.
// Sanity check that bufferStartTime is consistent with the expected value.
long
expectedBufferStartTime
=
audioTrackStartMediaTimeUs
+
long
expectedBufferStartTime
=
audioTrackStartMediaTimeUs
+
framesToDurationUs
(
submittedBytes
/
frameSize
);
framesToDurationUs
(
submittedBytes
/
frameSize
);
if
(
Math
.
abs
(
expectedBufferStartTime
-
bufferStartTime
)
>
200000
)
{
if
(
audioTrackStartMediaTimeState
==
START_IN_SYNC
&&
Math
.
abs
(
expectedBufferStartTime
-
bufferStartTime
)
>
200000
)
{
Log
.
e
(
TAG
,
"Discontinuity detected [expected "
+
expectedBufferStartTime
+
", got "
+
Log
.
e
(
TAG
,
"Discontinuity detected [expected "
+
expectedBufferStartTime
+
", got "
+
bufferStartTime
+
"]"
);
bufferStartTime
+
"]"
);
// Adjust audioTrackStartMediaTimeUs to compensate for the discontinuity. Also reset
audioTrackStartMediaTimeState
=
START_NEED_SYNC
;
// lastReportedCurrentPositionUs to allow time to jump backwards if it really wants to.
}
if
(
audioTrackStartMediaTimeState
==
START_NEED_SYNC
)
{
// Adjust audioTrackStartMediaTimeUs to be consistent with the current buffer's start
// time and the number of bytes submitted. Also reset lastReportedCurrentPositionUs to
// allow time to jump backwards if it really wants to.
audioTrackStartMediaTimeUs
+=
(
bufferStartTime
-
expectedBufferStartTime
);
audioTrackStartMediaTimeUs
+=
(
bufferStartTime
-
expectedBufferStartTime
);
lastReportedCurrentPositionUs
=
0
;
lastReportedCurrentPositionUs
=
0
;
}
}
...
...
library/src/main/java/com/google/android/exoplayer/MediaCodecTrackRenderer.java
View file @
4228f2cf
...
@@ -391,7 +391,9 @@ public abstract class MediaCodecTrackRenderer extends TrackRenderer {
...
@@ -391,7 +391,9 @@ public abstract class MediaCodecTrackRenderer extends TrackRenderer {
while
(
result
==
SampleSource
.
SAMPLE_READ
&&
currentPositionUs
<=
timeUs
)
{
while
(
result
==
SampleSource
.
SAMPLE_READ
&&
currentPositionUs
<=
timeUs
)
{
result
=
source
.
readData
(
trackIndex
,
currentPositionUs
,
formatHolder
,
sampleHolder
,
false
);
result
=
source
.
readData
(
trackIndex
,
currentPositionUs
,
formatHolder
,
sampleHolder
,
false
);
if
(
result
==
SampleSource
.
SAMPLE_READ
)
{
if
(
result
==
SampleSource
.
SAMPLE_READ
)
{
if
(!
sampleHolder
.
decodeOnly
)
{
currentPositionUs
=
sampleHolder
.
timeUs
;
currentPositionUs
=
sampleHolder
.
timeUs
;
}
codecCounters
.
discardedSamplesCount
++;
codecCounters
.
discardedSamplesCount
++;
}
else
if
(
result
==
SampleSource
.
FORMAT_READ
)
{
}
else
if
(
result
==
SampleSource
.
FORMAT_READ
)
{
onInputFormatChanged
(
formatHolder
);
onInputFormatChanged
(
formatHolder
);
...
@@ -677,15 +679,15 @@ public abstract class MediaCodecTrackRenderer extends TrackRenderer {
...
@@ -677,15 +679,15 @@ public abstract class MediaCodecTrackRenderer extends TrackRenderer {
return
false
;
return
false
;
}
}
if
(
decodeOnlyPresentationTimestamps
.
remove
(
outputBufferInfo
.
presentationTimeUs
))
{
boolean
decodeOnly
=
decodeOnlyPresentationTimestamps
.
contains
(
codec
.
releaseOutputBuffer
(
outputIndex
,
false
);
outputBufferInfo
.
presentationTimeUs
);
outputIndex
=
-
1
;
return
true
;
}
if
(
processOutputBuffer
(
timeUs
,
codec
,
outputBuffers
[
outputIndex
],
outputBufferInfo
,
if
(
processOutputBuffer
(
timeUs
,
codec
,
outputBuffers
[
outputIndex
],
outputBufferInfo
,
outputIndex
))
{
outputIndex
,
decodeOnly
))
{
if
(
decodeOnly
)
{
decodeOnlyPresentationTimestamps
.
remove
(
outputBufferInfo
.
presentationTimeUs
);
}
else
{
currentPositionUs
=
outputBufferInfo
.
presentationTimeUs
;
currentPositionUs
=
outputBufferInfo
.
presentationTimeUs
;
}
outputIndex
=
-
1
;
outputIndex
=
-
1
;
return
true
;
return
true
;
}
}
...
@@ -701,7 +703,8 @@ public abstract class MediaCodecTrackRenderer extends TrackRenderer {
...
@@ -701,7 +703,8 @@ public abstract class MediaCodecTrackRenderer extends TrackRenderer {
* @throws ExoPlaybackException If an error occurs processing the output buffer.
* @throws ExoPlaybackException If an error occurs processing the output buffer.
*/
*/
protected
abstract
boolean
processOutputBuffer
(
long
timeUs
,
MediaCodec
codec
,
ByteBuffer
buffer
,
protected
abstract
boolean
processOutputBuffer
(
long
timeUs
,
MediaCodec
codec
,
ByteBuffer
buffer
,
MediaCodec
.
BufferInfo
bufferInfo
,
int
bufferIndex
)
throws
ExoPlaybackException
;
MediaCodec
.
BufferInfo
bufferInfo
,
int
bufferIndex
,
boolean
shouldSkip
)
throws
ExoPlaybackException
;
/**
/**
* Returns the name of the secure variant of a given decoder.
* Returns the name of the secure variant of a given decoder.
...
...
library/src/main/java/com/google/android/exoplayer/MediaCodecVideoTrackRenderer.java
View file @
4228f2cf
...
@@ -29,7 +29,7 @@ import android.view.Surface;
...
@@ -29,7 +29,7 @@ import android.view.Surface;
import
java.nio.ByteBuffer
;
import
java.nio.ByteBuffer
;
/**
/**
* Decodes and renders video using {@MediaCodec}.
* Decodes and renders video using {@
link
MediaCodec}.
*/
*/
@TargetApi
(
16
)
@TargetApi
(
16
)
public
class
MediaCodecVideoTrackRenderer
extends
MediaCodecTrackRenderer
{
public
class
MediaCodecVideoTrackRenderer
extends
MediaCodecTrackRenderer
{
...
@@ -338,7 +338,12 @@ public class MediaCodecVideoTrackRenderer extends MediaCodecTrackRenderer {
...
@@ -338,7 +338,12 @@ public class MediaCodecVideoTrackRenderer extends MediaCodecTrackRenderer {
@Override
@Override
protected
boolean
processOutputBuffer
(
long
timeUs
,
MediaCodec
codec
,
ByteBuffer
buffer
,
protected
boolean
processOutputBuffer
(
long
timeUs
,
MediaCodec
codec
,
ByteBuffer
buffer
,
MediaCodec
.
BufferInfo
bufferInfo
,
int
bufferIndex
)
{
MediaCodec
.
BufferInfo
bufferInfo
,
int
bufferIndex
,
boolean
shouldSkip
)
{
if
(
shouldSkip
)
{
skipOutputBuffer
(
codec
,
bufferIndex
);
return
true
;
}
long
earlyUs
=
bufferInfo
.
presentationTimeUs
-
timeUs
;
long
earlyUs
=
bufferInfo
.
presentationTimeUs
-
timeUs
;
if
(
earlyUs
<
-
30000
)
{
if
(
earlyUs
<
-
30000
)
{
// We're more than 30ms late rendering the frame.
// We're more than 30ms late rendering the frame.
...
@@ -371,6 +376,13 @@ public class MediaCodecVideoTrackRenderer extends MediaCodecTrackRenderer {
...
@@ -371,6 +376,13 @@ public class MediaCodecVideoTrackRenderer extends MediaCodecTrackRenderer {
return
false
;
return
false
;
}
}
private
void
skipOutputBuffer
(
MediaCodec
codec
,
int
bufferIndex
)
{
TraceUtil
.
beginSection
(
"skipVideoBuffer"
);
codec
.
releaseOutputBuffer
(
bufferIndex
,
false
);
TraceUtil
.
endSection
();
codecCounters
.
skippedOutputBufferCount
++;
}
private
void
dropOutputBuffer
(
MediaCodec
codec
,
int
bufferIndex
)
{
private
void
dropOutputBuffer
(
MediaCodec
codec
,
int
bufferIndex
)
{
TraceUtil
.
beginSection
(
"dropVideoBuffer"
);
TraceUtil
.
beginSection
(
"dropVideoBuffer"
);
codec
.
releaseOutputBuffer
(
bufferIndex
,
false
);
codec
.
releaseOutputBuffer
(
bufferIndex
,
false
);
...
...
library/src/main/java/com/google/android/exoplayer/SampleSource.java
View file @
4228f2cf
...
@@ -54,8 +54,8 @@ public interface SampleSource {
...
@@ -54,8 +54,8 @@ public interface SampleSource {
* Prepares the source.
* Prepares the source.
* <p>
* <p>
* Preparation may require reading from the data source (e.g. to determine the available tracks
* Preparation may require reading from the data source (e.g. to determine the available tracks
* and formats). If insufficient data is available then the call will return
rather than block.
* and formats). If insufficient data is available then the call will return
{@code false} rather
* The method can be called repeatedly until the return value indicates success.
*
than block.
The method can be called repeatedly until the return value indicates success.
*
*
* @return True if the source was prepared successfully, false otherwise.
* @return True if the source was prepared successfully, false otherwise.
* @throws IOException If an error occurred preparing the source.
* @throws IOException If an error occurred preparing the source.
...
...
library/src/main/java/com/google/android/exoplayer/chunk/ChunkSampleSource.java
View file @
4228f2cf
...
@@ -59,7 +59,7 @@ public class ChunkSampleSource implements SampleSource, Loader.Listener {
...
@@ -59,7 +59,7 @@ public class ChunkSampleSource implements SampleSource, Loader.Listener {
* load is for initialization data.
* load is for initialization data.
* @param totalBytes The length of the data being loaded in bytes.
* @param totalBytes The length of the data being loaded in bytes.
*/
*/
void
onLoadStarted
(
int
sourceId
,
int
formatId
,
int
trigger
,
boolean
isInitialization
,
void
onLoadStarted
(
int
sourceId
,
String
formatId
,
int
trigger
,
boolean
isInitialization
,
int
mediaStartTimeMs
,
int
mediaEndTimeMs
,
long
totalBytes
);
int
mediaStartTimeMs
,
int
mediaEndTimeMs
,
long
totalBytes
);
/**
/**
...
@@ -126,7 +126,7 @@ public class ChunkSampleSource implements SampleSource, Loader.Listener {
...
@@ -126,7 +126,7 @@ public class ChunkSampleSource implements SampleSource, Loader.Listener {
* {@link ChunkSource}.
* {@link ChunkSource}.
* @param mediaTimeMs The media time at which the change occurred.
* @param mediaTimeMs The media time at which the change occurred.
*/
*/
void
onDownstreamFormatChanged
(
int
sourceId
,
int
formatId
,
int
trigger
,
int
mediaTimeMs
);
void
onDownstreamFormatChanged
(
int
sourceId
,
String
formatId
,
int
trigger
,
int
mediaTimeMs
);
}
}
...
@@ -160,6 +160,7 @@ public class ChunkSampleSource implements SampleSource, Loader.Listener {
...
@@ -160,6 +160,7 @@ public class ChunkSampleSource implements SampleSource, Loader.Listener {
private
int
currentLoadableExceptionCount
;
private
int
currentLoadableExceptionCount
;
private
long
currentLoadableExceptionTimestamp
;
private
long
currentLoadableExceptionTimestamp
;
private
MediaFormat
downstreamMediaFormat
;
private
volatile
Format
downstreamFormat
;
private
volatile
Format
downstreamFormat
;
public
ChunkSampleSource
(
ChunkSource
chunkSource
,
LoadControl
loadControl
,
public
ChunkSampleSource
(
ChunkSource
chunkSource
,
LoadControl
loadControl
,
...
@@ -221,6 +222,7 @@ public class ChunkSampleSource implements SampleSource, Loader.Listener {
...
@@ -221,6 +222,7 @@ public class ChunkSampleSource implements SampleSource, Loader.Listener {
chunkSource
.
enable
();
chunkSource
.
enable
();
loadControl
.
register
(
this
,
bufferSizeContribution
);
loadControl
.
register
(
this
,
bufferSizeContribution
);
downstreamFormat
=
null
;
downstreamFormat
=
null
;
downstreamMediaFormat
=
null
;
downstreamPositionUs
=
timeUs
;
downstreamPositionUs
=
timeUs
;
lastSeekPositionUs
=
timeUs
;
lastSeekPositionUs
=
timeUs
;
restartFrom
(
timeUs
);
restartFrom
(
timeUs
);
...
@@ -288,21 +290,30 @@ public class ChunkSampleSource implements SampleSource, Loader.Listener {
...
@@ -288,21 +290,30 @@ public class ChunkSampleSource implements SampleSource, Loader.Listener {
return
readData
(
track
,
playbackPositionUs
,
formatHolder
,
sampleHolder
,
false
);
return
readData
(
track
,
playbackPositionUs
,
formatHolder
,
sampleHolder
,
false
);
}
else
if
(
mediaChunk
.
isLastChunk
())
{
}
else
if
(
mediaChunk
.
isLastChunk
())
{
return
END_OF_STREAM
;
return
END_OF_STREAM
;
}
else
{
}
IOException
chunkSourceException
=
chunkSource
.
getError
();
IOException
chunkSourceException
=
chunkSource
.
getError
();
if
(
chunkSourceException
!=
null
)
{
if
(
chunkSourceException
!=
null
)
{
throw
chunkSourceException
;
throw
chunkSourceException
;
}
}
return
NOTHING_READ
;
return
NOTHING_READ
;
}
}
}
else
if
(
downstreamFormat
==
null
||
downstreamFormat
.
id
!=
mediaChunk
.
format
.
id
)
{
if
(
downstreamFormat
==
null
||
!
downstreamFormat
.
equals
(
mediaChunk
.
format
))
{
notifyDownstreamFormatChanged
(
mediaChunk
.
format
.
id
,
mediaChunk
.
trigger
,
notifyDownstreamFormatChanged
(
mediaChunk
.
format
.
id
,
mediaChunk
.
trigger
,
mediaChunk
.
startTimeUs
);
mediaChunk
.
startTimeUs
);
MediaFormat
format
=
mediaChunk
.
getMediaFormat
();
chunkSource
.
getMaxVideoDimensions
(
format
);
formatHolder
.
format
=
format
;
formatHolder
.
drmInitData
=
mediaChunk
.
getPsshInfo
();
downstreamFormat
=
mediaChunk
.
format
;
downstreamFormat
=
mediaChunk
.
format
;
}
if
(!
mediaChunk
.
prepare
())
{
return
NOTHING_READ
;
}
MediaFormat
mediaFormat
=
mediaChunk
.
getMediaFormat
();
if
(
mediaFormat
!=
null
&&
!
mediaFormat
.
equals
(
downstreamMediaFormat
))
{
chunkSource
.
getMaxVideoDimensions
(
mediaFormat
);
formatHolder
.
format
=
mediaFormat
;
formatHolder
.
drmInitData
=
mediaChunk
.
getPsshInfo
();
downstreamMediaFormat
=
mediaFormat
;
return
FORMAT_READ
;
return
FORMAT_READ
;
}
}
...
@@ -430,6 +441,7 @@ public class ChunkSampleSource implements SampleSource, Loader.Listener {
...
@@ -430,6 +441,7 @@ public class ChunkSampleSource implements SampleSource, Loader.Listener {
currentLoadableExceptionCount
++;
currentLoadableExceptionCount
++;
currentLoadableExceptionTimestamp
=
SystemClock
.
elapsedRealtime
();
currentLoadableExceptionTimestamp
=
SystemClock
.
elapsedRealtime
();
notifyUpstreamError
(
e
);
notifyUpstreamError
(
e
);
chunkSource
.
onChunkLoadError
(
currentLoadableHolder
.
chunk
,
e
);
updateLoadControl
();
updateLoadControl
();
}
}
...
@@ -653,7 +665,7 @@ public class ChunkSampleSource implements SampleSource, Loader.Listener {
...
@@ -653,7 +665,7 @@ public class ChunkSampleSource implements SampleSource, Loader.Listener {
return
(
int
)
(
timeUs
/
1000
);
return
(
int
)
(
timeUs
/
1000
);
}
}
private
void
notifyLoadStarted
(
final
int
formatId
,
final
int
trigger
,
private
void
notifyLoadStarted
(
final
String
formatId
,
final
int
trigger
,
final
boolean
isInitialization
,
final
long
mediaStartTimeUs
,
final
long
mediaEndTimeUs
,
final
boolean
isInitialization
,
final
long
mediaStartTimeUs
,
final
long
mediaEndTimeUs
,
final
long
totalBytes
)
{
final
long
totalBytes
)
{
if
(
eventHandler
!=
null
&&
eventListener
!=
null
)
{
if
(
eventHandler
!=
null
&&
eventListener
!=
null
)
{
...
@@ -724,7 +736,7 @@ public class ChunkSampleSource implements SampleSource, Loader.Listener {
...
@@ -724,7 +736,7 @@ public class ChunkSampleSource implements SampleSource, Loader.Listener {
}
}
}
}
private
void
notifyDownstreamFormatChanged
(
final
int
formatId
,
final
int
trigger
,
private
void
notifyDownstreamFormatChanged
(
final
String
formatId
,
final
int
trigger
,
final
long
mediaTimeUs
)
{
final
long
mediaTimeUs
)
{
if
(
eventHandler
!=
null
&&
eventListener
!=
null
)
{
if
(
eventHandler
!=
null
&&
eventListener
!=
null
)
{
eventHandler
.
post
(
new
Runnable
()
{
eventHandler
.
post
(
new
Runnable
()
{
...
...
library/src/main/java/com/google/android/exoplayer/chunk/ChunkSource.java
View file @
4228f2cf
...
@@ -58,7 +58,7 @@ public interface ChunkSource {
...
@@ -58,7 +58,7 @@ public interface ChunkSource {
*
*
* @param queue A representation of the currently buffered {@link MediaChunk}s.
* @param queue A representation of the currently buffered {@link MediaChunk}s.
*/
*/
void
disable
(
List
<
MediaChunk
>
queue
);
void
disable
(
List
<
?
extends
MediaChunk
>
queue
);
/**
/**
* Indicates to the source that it should still be checking for updates to the stream.
* Indicates to the source that it should still be checking for updates to the stream.
...
@@ -100,4 +100,13 @@ public interface ChunkSource {
...
@@ -100,4 +100,13 @@ public interface ChunkSource {
*/
*/
IOException
getError
();
IOException
getError
();
/**
* Invoked when the {@link ChunkSampleSource} encounters an error loading a chunk obtained from
* this source.
*
* @param chunk The chunk whose load encountered the error.
* @param e The error.
*/
void
onChunkLoadError
(
Chunk
chunk
,
Exception
e
);
}
}
library/src/main/java/com/google/android/exoplayer/chunk/Format.java
View file @
4228f2cf
...
@@ -15,12 +15,14 @@
...
@@ -15,12 +15,14 @@
*/
*/
package
com
.
google
.
android
.
exoplayer
.
chunk
;
package
com
.
google
.
android
.
exoplayer
.
chunk
;
import
com.google.android.exoplayer.util.Assertions
;
import
java.util.Comparator
;
import
java.util.Comparator
;
/**
/**
* A format definition for streams.
* A format definition for streams.
*/
*/
public
final
class
Format
{
public
class
Format
{
/**
/**
* Sorts {@link Format} objects in order of decreasing bandwidth.
* Sorts {@link Format} objects in order of decreasing bandwidth.
...
@@ -29,7 +31,7 @@ public final class Format {
...
@@ -29,7 +31,7 @@ public final class Format {
@Override
@Override
public
int
compare
(
Format
a
,
Format
b
)
{
public
int
compare
(
Format
a
,
Format
b
)
{
return
b
.
b
andwidth
-
a
.
bandwidth
;
return
b
.
b
itrate
-
a
.
bitrate
;
}
}
}
}
...
@@ -37,7 +39,7 @@ public final class Format {
...
@@ -37,7 +39,7 @@ public final class Format {
/**
/**
* An identifier for the format.
* An identifier for the format.
*/
*/
public
final
int
id
;
public
final
String
id
;
/**
/**
* The mime type of the format.
* The mime type of the format.
...
@@ -65,8 +67,16 @@ public final class Format {
...
@@ -65,8 +67,16 @@ public final class Format {
public
final
int
audioSamplingRate
;
public
final
int
audioSamplingRate
;
/**
/**
* The average bandwidth in bits per second.
*/
public
final
int
bitrate
;
/**
* The average bandwidth in bytes per second.
* The average bandwidth in bytes per second.
*
* @deprecated Use {@link #bitrate}. However note that the units of measurement are different.
*/
*/
@Deprecated
public
final
int
bandwidth
;
public
final
int
bandwidth
;
/**
/**
...
@@ -76,17 +86,38 @@ public final class Format {
...
@@ -76,17 +86,38 @@ public final class Format {
* @param height The height of the video in pixels, or -1 for non-video formats.
* @param height The height of the video in pixels, or -1 for non-video formats.
* @param numChannels The number of audio channels, or -1 for non-audio formats.
* @param numChannels The number of audio channels, or -1 for non-audio formats.
* @param audioSamplingRate The audio sampling rate in Hz, or -1 for non-audio formats.
* @param audioSamplingRate The audio sampling rate in Hz, or -1 for non-audio formats.
* @param b
andwidth The average bandwidth of the format in byte
s per second.
* @param b
itrate The average bandwidth of the format in bit
s per second.
*/
*/
public
Format
(
int
id
,
String
mimeType
,
int
width
,
int
height
,
int
numChannels
,
public
Format
(
String
id
,
String
mimeType
,
int
width
,
int
height
,
int
numChannels
,
int
audioSamplingRate
,
int
b
andwidth
)
{
int
audioSamplingRate
,
int
b
itrate
)
{
this
.
id
=
id
;
this
.
id
=
Assertions
.
checkNotNull
(
id
)
;
this
.
mimeType
=
mimeType
;
this
.
mimeType
=
mimeType
;
this
.
width
=
width
;
this
.
width
=
width
;
this
.
height
=
height
;
this
.
height
=
height
;
this
.
numChannels
=
numChannels
;
this
.
numChannels
=
numChannels
;
this
.
audioSamplingRate
=
audioSamplingRate
;
this
.
audioSamplingRate
=
audioSamplingRate
;
this
.
bandwidth
=
bandwidth
;
this
.
bitrate
=
bitrate
;
this
.
bandwidth
=
bitrate
/
8
;
}
@Override
public
int
hashCode
()
{
return
id
.
hashCode
();
}
/**
* Implements equality based on {@link #id} only.
*/
@Override
public
boolean
equals
(
Object
obj
)
{
if
(
this
==
obj
)
{
return
true
;
}
if
(
obj
==
null
||
getClass
()
!=
obj
.
getClass
())
{
return
false
;
}
Format
other
=
(
Format
)
obj
;
return
other
.
id
.
equals
(
id
);
}
}
}
}
library/src/main/java/com/google/android/exoplayer/chunk/FormatEvaluator.java
View file @
4228f2cf
...
@@ -146,7 +146,7 @@ public interface FormatEvaluator {
...
@@ -146,7 +146,7 @@ public interface FormatEvaluator {
public
void
evaluate
(
List
<?
extends
MediaChunk
>
queue
,
long
playbackPositionUs
,
public
void
evaluate
(
List
<?
extends
MediaChunk
>
queue
,
long
playbackPositionUs
,
Format
[]
formats
,
Evaluation
evaluation
)
{
Format
[]
formats
,
Evaluation
evaluation
)
{
Format
newFormat
=
formats
[
random
.
nextInt
(
formats
.
length
)];
Format
newFormat
=
formats
[
random
.
nextInt
(
formats
.
length
)];
if
(
evaluation
.
format
!=
null
&&
evaluation
.
format
.
id
!=
newFormat
.
id
)
{
if
(
evaluation
.
format
!=
null
&&
!
evaluation
.
format
.
id
.
equals
(
newFormat
.
id
)
)
{
evaluation
.
trigger
=
TRIGGER_ADAPTIVE
;
evaluation
.
trigger
=
TRIGGER_ADAPTIVE
;
}
}
evaluation
.
format
=
newFormat
;
evaluation
.
format
=
newFormat
;
...
@@ -236,8 +236,8 @@ public interface FormatEvaluator {
...
@@ -236,8 +236,8 @@ public interface FormatEvaluator {
:
queue
.
get
(
queue
.
size
()
-
1
).
endTimeUs
-
playbackPositionUs
;
:
queue
.
get
(
queue
.
size
()
-
1
).
endTimeUs
-
playbackPositionUs
;
Format
current
=
evaluation
.
format
;
Format
current
=
evaluation
.
format
;
Format
ideal
=
determineIdealFormat
(
formats
,
bandwidthMeter
.
getEstimate
());
Format
ideal
=
determineIdealFormat
(
formats
,
bandwidthMeter
.
getEstimate
());
boolean
isHigher
=
ideal
!=
null
&&
current
!=
null
&&
ideal
.
b
andwidth
>
current
.
bandwidth
;
boolean
isHigher
=
ideal
!=
null
&&
current
!=
null
&&
ideal
.
b
itrate
>
current
.
bitrate
;
boolean
isLower
=
ideal
!=
null
&&
current
!=
null
&&
ideal
.
b
andwidth
<
current
.
bandwidth
;
boolean
isLower
=
ideal
!=
null
&&
current
!=
null
&&
ideal
.
b
itrate
<
current
.
bitrate
;
if
(
isHigher
)
{
if
(
isHigher
)
{
if
(
bufferedDurationUs
<
minDurationForQualityIncreaseUs
)
{
if
(
bufferedDurationUs
<
minDurationForQualityIncreaseUs
)
{
// The ideal format is a higher quality, but we have insufficient buffer to
// The ideal format is a higher quality, but we have insufficient buffer to
...
@@ -247,11 +247,11 @@ public interface FormatEvaluator {
...
@@ -247,11 +247,11 @@ public interface FormatEvaluator {
// We're switching from an SD stream to a stream of higher resolution. Consider
// We're switching from an SD stream to a stream of higher resolution. Consider
// discarding already buffered media chunks. Specifically, discard media chunks starting
// discarding already buffered media chunks. Specifically, discard media chunks starting
// from the first one that is of lower bandwidth, lower resolution and that is not HD.
// from the first one that is of lower bandwidth, lower resolution and that is not HD.
for
(
int
i
=
0
;
i
<
queue
.
size
();
i
++)
{
for
(
int
i
=
1
;
i
<
queue
.
size
();
i
++)
{
MediaChunk
thisChunk
=
queue
.
get
(
i
);
MediaChunk
thisChunk
=
queue
.
get
(
i
);
long
durationBeforeThisSegmentUs
=
thisChunk
.
startTimeUs
-
playbackPositionUs
;
long
durationBeforeThisSegmentUs
=
thisChunk
.
startTimeUs
-
playbackPositionUs
;
if
(
durationBeforeThisSegmentUs
>=
minDurationToRetainAfterDiscardUs
if
(
durationBeforeThisSegmentUs
>=
minDurationToRetainAfterDiscardUs
&&
thisChunk
.
format
.
b
andwidth
<
ideal
.
bandwidth
&&
thisChunk
.
format
.
b
itrate
<
ideal
.
bitrate
&&
thisChunk
.
format
.
height
<
ideal
.
height
&&
thisChunk
.
format
.
height
<
ideal
.
height
&&
thisChunk
.
format
.
height
<
720
&&
thisChunk
.
format
.
height
<
720
&&
thisChunk
.
format
.
width
<
1280
)
{
&&
thisChunk
.
format
.
width
<
1280
)
{
...
@@ -280,7 +280,7 @@ public interface FormatEvaluator {
...
@@ -280,7 +280,7 @@ public interface FormatEvaluator {
long
effectiveBandwidth
=
computeEffectiveBandwidthEstimate
(
bandwidthEstimate
);
long
effectiveBandwidth
=
computeEffectiveBandwidthEstimate
(
bandwidthEstimate
);
for
(
int
i
=
0
;
i
<
formats
.
length
;
i
++)
{
for
(
int
i
=
0
;
i
<
formats
.
length
;
i
++)
{
Format
format
=
formats
[
i
];
Format
format
=
formats
[
i
];
if
(
format
.
bandwidth
<=
effectiveBandwidth
)
{
if
(
(
format
.
bitrate
/
8
)
<=
effectiveBandwidth
)
{
return
format
;
return
format
;
}
}
}
}
...
...
library/src/main/java/com/google/android/exoplayer/chunk/MediaChunk.java
View file @
4228f2cf
...
@@ -73,9 +73,7 @@ public abstract class MediaChunk extends Chunk {
...
@@ -73,9 +73,7 @@ public abstract class MediaChunk extends Chunk {
/**
/**
* Seeks to the beginning of the chunk.
* Seeks to the beginning of the chunk.
*/
*/
public
final
void
seekToStart
()
{
public
abstract
void
seekToStart
();
seekTo
(
startTimeUs
,
false
);
}
/**
/**
* Seeks to the specified position within the chunk.
* Seeks to the specified position within the chunk.
...
@@ -90,7 +88,21 @@ public abstract class MediaChunk extends Chunk {
...
@@ -90,7 +88,21 @@ public abstract class MediaChunk extends Chunk {
public
abstract
boolean
seekTo
(
long
positionUs
,
boolean
allowNoop
);
public
abstract
boolean
seekTo
(
long
positionUs
,
boolean
allowNoop
);
/**
/**
* Prepares the chunk for reading. Does nothing if the chunk is already prepared.
* <p>
* Preparation may require consuming some of the chunk. If the data is not yet available then
* this method will return {@code false} rather than block. The method can be called repeatedly
* until the return value indicates success.
*
* @return True if the chunk was prepared. False otherwise.
* @throws ParserException If an error occurs parsing the media data.
*/
public
abstract
boolean
prepare
()
throws
ParserException
;
/**
* Reads the next media sample from the chunk.
* Reads the next media sample from the chunk.
* <p>
* Should only be called after the chunk has been successfully prepared.
*
*
* @param holder A holder to store the read sample.
* @param holder A holder to store the read sample.
* @return True if a sample was read. False if more data is still required.
* @return True if a sample was read. False if more data is still required.
...
@@ -101,6 +113,8 @@ public abstract class MediaChunk extends Chunk {
...
@@ -101,6 +113,8 @@ public abstract class MediaChunk extends Chunk {
/**
/**
* Returns the media format of the samples contained within this chunk.
* Returns the media format of the samples contained within this chunk.
* <p>
* Should only be called after the chunk has been successfully prepared.
*
*
* @return The sample media format.
* @return The sample media format.
*/
*/
...
@@ -108,6 +122,8 @@ public abstract class MediaChunk extends Chunk {
...
@@ -108,6 +122,8 @@ public abstract class MediaChunk extends Chunk {
/**
/**
* Returns the pssh information associated with the chunk.
* Returns the pssh information associated with the chunk.
* <p>
* Should only be called after the chunk has been successfully prepared.
*
*
* @return The pssh information.
* @return The pssh information.
*/
*/
...
...
library/src/main/java/com/google/android/exoplayer/chunk/Mp4MediaChunk.java
View file @
4228f2cf
...
@@ -33,28 +33,44 @@ import java.util.UUID;
...
@@ -33,28 +33,44 @@ import java.util.UUID;
public
final
class
Mp4MediaChunk
extends
MediaChunk
{
public
final
class
Mp4MediaChunk
extends
MediaChunk
{
private
final
FragmentedMp4Extractor
extractor
;
private
final
FragmentedMp4Extractor
extractor
;
private
final
boolean
maybeSelfContained
;
private
final
long
sampleOffsetUs
;
private
final
long
sampleOffsetUs
;
private
boolean
prepared
;
private
MediaFormat
mediaFormat
;
private
Map
<
UUID
,
byte
[]>
psshInfo
;
/**
/**
* @param dataSource A {@link DataSource} for loading the data.
* @param dataSource A {@link DataSource} for loading the data.
* @param dataSpec Defines the data to be loaded.
* @param dataSpec Defines the data to be loaded.
* @param format The format of the stream to which this chunk belongs.
* @param format The format of the stream to which this chunk belongs.
* @param extractor The extractor that will be used to extract the samples.
* @param trigger The reason for this chunk being selected.
* @param trigger The reason for this chunk being selected.
* @param startTimeUs The start time of the media contained by the chunk, in microseconds.
* @param startTimeUs The start time of the media contained by the chunk, in microseconds.
* @param endTimeUs The end time of the media contained by the chunk, in microseconds.
* @param endTimeUs The end time of the media contained by the chunk, in microseconds.
* @param sampleOffsetUs An offset to subtract from the sample timestamps parsed by the extractor.
* @param nextChunkIndex The index of the next chunk, or -1 if this is the last chunk.
* @param nextChunkIndex The index of the next chunk, or -1 if this is the last chunk.
* @param extractor The extractor that will be used to extract the samples.
* @param maybeSelfContained Set to true if this chunk might be self contained, meaning it might
* contain a moov atom defining the media format of the chunk. This parameter can always be
* safely set to true. Setting to false where the chunk is known to not be self contained may
* improve startup latency.
* @param sampleOffsetUs An offset to subtract from the sample timestamps parsed by the extractor.
*/
*/
public
Mp4MediaChunk
(
DataSource
dataSource
,
DataSpec
dataSpec
,
Format
format
,
public
Mp4MediaChunk
(
DataSource
dataSource
,
DataSpec
dataSpec
,
Format
format
,
int
trigger
,
FragmentedMp4Extractor
extractor
,
long
startTimeUs
,
long
endTimeUs
,
int
trigger
,
long
startTimeUs
,
long
endTimeUs
,
int
nextChunkIndex
,
long
sampleOffsetUs
,
int
nextChunkIndex
)
{
FragmentedMp4Extractor
extractor
,
boolean
maybeSelfContained
,
long
sampleOffsetUs
)
{
super
(
dataSource
,
dataSpec
,
format
,
trigger
,
startTimeUs
,
endTimeUs
,
nextChunkIndex
);
super
(
dataSource
,
dataSpec
,
format
,
trigger
,
startTimeUs
,
endTimeUs
,
nextChunkIndex
);
this
.
extractor
=
extractor
;
this
.
extractor
=
extractor
;
this
.
maybeSelfContained
=
maybeSelfContained
;
this
.
sampleOffsetUs
=
sampleOffsetUs
;
this
.
sampleOffsetUs
=
sampleOffsetUs
;
}
}
@Override
@Override
public
void
seekToStart
()
{
extractor
.
seekTo
(
0
,
false
);
resetReadPosition
();
}
@Override
public
boolean
seekTo
(
long
positionUs
,
boolean
allowNoop
)
{
public
boolean
seekTo
(
long
positionUs
,
boolean
allowNoop
)
{
long
seekTimeUs
=
positionUs
+
sampleOffsetUs
;
long
seekTimeUs
=
positionUs
+
sampleOffsetUs
;
boolean
isDiscontinuous
=
extractor
.
seekTo
(
seekTimeUs
,
allowNoop
);
boolean
isDiscontinuous
=
extractor
.
seekTo
(
seekTimeUs
,
allowNoop
);
...
@@ -65,6 +81,29 @@ public final class Mp4MediaChunk extends MediaChunk {
...
@@ -65,6 +81,29 @@ public final class Mp4MediaChunk extends MediaChunk {
}
}
@Override
@Override
public
boolean
prepare
()
throws
ParserException
{
if
(!
prepared
)
{
if
(
maybeSelfContained
)
{
// Read up to the first sample. Once we're there, we know that the extractor must have
// parsed a moov atom if the chunk contains one.
NonBlockingInputStream
inputStream
=
getNonBlockingInputStream
();
Assertions
.
checkState
(
inputStream
!=
null
);
int
result
=
extractor
.
read
(
inputStream
,
null
);
prepared
=
(
result
&
FragmentedMp4Extractor
.
RESULT_NEED_SAMPLE_HOLDER
)
!=
0
;
}
else
{
// We know there isn't a moov atom. The extractor must have parsed one from a separate
// initialization chunk.
prepared
=
true
;
}
if
(
prepared
)
{
mediaFormat
=
Assertions
.
checkNotNull
(
extractor
.
getFormat
());
psshInfo
=
extractor
.
getPsshInfo
();
}
}
return
prepared
;
}
@Override
public
boolean
read
(
SampleHolder
holder
)
throws
ParserException
{
public
boolean
read
(
SampleHolder
holder
)
throws
ParserException
{
NonBlockingInputStream
inputStream
=
getNonBlockingInputStream
();
NonBlockingInputStream
inputStream
=
getNonBlockingInputStream
();
Assertions
.
checkState
(
inputStream
!=
null
);
Assertions
.
checkState
(
inputStream
!=
null
);
...
@@ -78,12 +117,12 @@ public final class Mp4MediaChunk extends MediaChunk {
...
@@ -78,12 +117,12 @@ public final class Mp4MediaChunk extends MediaChunk {
@Override
@Override
public
MediaFormat
getMediaFormat
()
{
public
MediaFormat
getMediaFormat
()
{
return
extractor
.
getFormat
()
;
return
mediaFormat
;
}
}
@Override
@Override
public
Map
<
UUID
,
byte
[]>
getPsshInfo
()
{
public
Map
<
UUID
,
byte
[]>
getPsshInfo
()
{
return
extractor
.
getPsshInfo
()
;
return
psshInfo
;
}
}
}
}
library/src/main/java/com/google/android/exoplayer/chunk/MultiTrackChunkSource.java
View file @
4228f2cf
...
@@ -68,7 +68,7 @@ public class MultiTrackChunkSource implements ChunkSource, ExoPlayerComponent {
...
@@ -68,7 +68,7 @@ public class MultiTrackChunkSource implements ChunkSource, ExoPlayerComponent {
}
}
@Override
@Override
public
void
disable
(
List
<
MediaChunk
>
queue
)
{
public
void
disable
(
List
<
?
extends
MediaChunk
>
queue
)
{
selectedSource
.
disable
(
queue
);
selectedSource
.
disable
(
queue
);
enabled
=
false
;
enabled
=
false
;
}
}
...
@@ -102,4 +102,9 @@ public class MultiTrackChunkSource implements ChunkSource, ExoPlayerComponent {
...
@@ -102,4 +102,9 @@ public class MultiTrackChunkSource implements ChunkSource, ExoPlayerComponent {
}
}
}
}
@Override
public
void
onChunkLoadError
(
Chunk
chunk
,
Exception
e
)
{
selectedSource
.
onChunkLoadError
(
chunk
,
e
);
}
}
}
library/src/main/java/com/google/android/exoplayer/chunk/SingleSampleMediaChunk.java
View file @
4228f2cf
...
@@ -78,6 +78,11 @@ public class SingleSampleMediaChunk extends MediaChunk {
...
@@ -78,6 +78,11 @@ public class SingleSampleMediaChunk extends MediaChunk {
}
}
@Override
@Override
public
boolean
prepare
()
{
return
true
;
}
@Override
public
boolean
read
(
SampleHolder
holder
)
{
public
boolean
read
(
SampleHolder
holder
)
{
NonBlockingInputStream
inputStream
=
getNonBlockingInputStream
();
NonBlockingInputStream
inputStream
=
getNonBlockingInputStream
();
Assertions
.
checkState
(
inputStream
!=
null
);
Assertions
.
checkState
(
inputStream
!=
null
);
...
@@ -110,6 +115,11 @@ public class SingleSampleMediaChunk extends MediaChunk {
...
@@ -110,6 +115,11 @@ public class SingleSampleMediaChunk extends MediaChunk {
}
}
@Override
@Override
public
void
seekToStart
()
{
resetReadPosition
();
}
@Override
public
boolean
seekTo
(
long
positionUs
,
boolean
allowNoop
)
{
public
boolean
seekTo
(
long
positionUs
,
boolean
allowNoop
)
{
resetReadPosition
();
resetReadPosition
();
return
true
;
return
true
;
...
...
library/src/main/java/com/google/android/exoplayer/chunk/WebmMediaChunk.java
View file @
4228f2cf
...
@@ -51,6 +51,11 @@ public final class WebmMediaChunk extends MediaChunk {
...
@@ -51,6 +51,11 @@ public final class WebmMediaChunk extends MediaChunk {
}
}
@Override
@Override
public
void
seekToStart
()
{
seekTo
(
0
,
false
);
}
@Override
public
boolean
seekTo
(
long
positionUs
,
boolean
allowNoop
)
{
public
boolean
seekTo
(
long
positionUs
,
boolean
allowNoop
)
{
boolean
isDiscontinuous
=
extractor
.
seekTo
(
positionUs
,
allowNoop
);
boolean
isDiscontinuous
=
extractor
.
seekTo
(
positionUs
,
allowNoop
);
if
(
isDiscontinuous
)
{
if
(
isDiscontinuous
)
{
...
@@ -60,6 +65,11 @@ public final class WebmMediaChunk extends MediaChunk {
...
@@ -60,6 +65,11 @@ public final class WebmMediaChunk extends MediaChunk {
}
}
@Override
@Override
public
boolean
prepare
()
{
return
true
;
}
@Override
public
boolean
read
(
SampleHolder
holder
)
{
public
boolean
read
(
SampleHolder
holder
)
{
NonBlockingInputStream
inputStream
=
getNonBlockingInputStream
();
NonBlockingInputStream
inputStream
=
getNonBlockingInputStream
();
Assertions
.
checkState
(
inputStream
!=
null
);
Assertions
.
checkState
(
inputStream
!=
null
);
...
...
library/src/main/java/com/google/android/exoplayer/dash/DashMp4ChunkSource.java
View file @
4228f2cf
...
@@ -27,18 +27,18 @@ import com.google.android.exoplayer.chunk.FormatEvaluator;
...
@@ -27,18 +27,18 @@ import com.google.android.exoplayer.chunk.FormatEvaluator;
import
com.google.android.exoplayer.chunk.FormatEvaluator.Evaluation
;
import
com.google.android.exoplayer.chunk.FormatEvaluator.Evaluation
;
import
com.google.android.exoplayer.chunk.MediaChunk
;
import
com.google.android.exoplayer.chunk.MediaChunk
;
import
com.google.android.exoplayer.chunk.Mp4MediaChunk
;
import
com.google.android.exoplayer.chunk.Mp4MediaChunk
;
import
com.google.android.exoplayer.dash.mpd.RangedUri
;
import
com.google.android.exoplayer.dash.mpd.Representation
;
import
com.google.android.exoplayer.dash.mpd.Representation
;
import
com.google.android.exoplayer.parser.SegmentIndex
;
import
com.google.android.exoplayer.parser.mp4.FragmentedMp4Extractor
;
import
com.google.android.exoplayer.parser.mp4.FragmentedMp4Extractor
;
import
com.google.android.exoplayer.upstream.DataSource
;
import
com.google.android.exoplayer.upstream.DataSource
;
import
com.google.android.exoplayer.upstream.DataSpec
;
import
com.google.android.exoplayer.upstream.DataSpec
;
import
com.google.android.exoplayer.upstream.NonBlockingInputStream
;
import
com.google.android.exoplayer.upstream.NonBlockingInputStream
;
import
android.util.Log
;
import
android.net.Uri
;
import
android.util.SparseArray
;
import
java.io.IOException
;
import
java.io.IOException
;
import
java.util.Arrays
;
import
java.util.Arrays
;
import
java.util.HashMap
;
import
java.util.List
;
import
java.util.List
;
/**
/**
...
@@ -46,26 +46,17 @@ import java.util.List;
...
@@ -46,26 +46,17 @@ import java.util.List;
*/
*/
public
class
DashMp4ChunkSource
implements
ChunkSource
{
public
class
DashMp4ChunkSource
implements
ChunkSource
{
public
static
final
int
DEFAULT_NUM_SEGMENTS_PER_CHUNK
=
1
;
private
static
final
int
EXPECTED_INITIALIZATION_RESULT
=
FragmentedMp4Extractor
.
RESULT_END_OF_STREAM
|
FragmentedMp4Extractor
.
RESULT_READ_MOOV
|
FragmentedMp4Extractor
.
RESULT_READ_SIDX
;
private
static
final
String
TAG
=
"DashMp4ChunkSource"
;
private
final
TrackInfo
trackInfo
;
private
final
TrackInfo
trackInfo
;
private
final
DataSource
dataSource
;
private
final
DataSource
dataSource
;
private
final
FormatEvaluator
evaluator
;
private
final
FormatEvaluator
evaluator
;
private
final
Evaluation
evaluation
;
private
final
Evaluation
evaluation
;
private
final
int
maxWidth
;
private
final
int
maxWidth
;
private
final
int
maxHeight
;
private
final
int
maxHeight
;
private
final
int
numSegmentsPerChunk
;
private
final
Format
[]
formats
;
private
final
Format
[]
formats
;
private
final
SparseArray
<
Representation
>
representations
;
private
final
HashMap
<
String
,
Representation
>
representations
;
private
final
SparseArray
<
FragmentedMp4Extractor
>
extractors
;
private
final
HashMap
<
String
,
FragmentedMp4Extractor
>
extractors
;
private
final
HashMap
<
String
,
DashSegmentIndex
>
segmentIndexes
;
private
boolean
lastChunkWasInitialization
;
private
boolean
lastChunkWasInitialization
;
...
@@ -76,26 +67,14 @@ public class DashMp4ChunkSource implements ChunkSource {
...
@@ -76,26 +67,14 @@ public class DashMp4ChunkSource implements ChunkSource {
*/
*/
public
DashMp4ChunkSource
(
DataSource
dataSource
,
FormatEvaluator
evaluator
,
public
DashMp4ChunkSource
(
DataSource
dataSource
,
FormatEvaluator
evaluator
,
Representation
...
representations
)
{
Representation
...
representations
)
{
this
(
dataSource
,
evaluator
,
DEFAULT_NUM_SEGMENTS_PER_CHUNK
,
representations
);
}
/**
* @param dataSource A {@link DataSource} suitable for loading the media data.
* @param evaluator Selects from the available formats.
* @param numSegmentsPerChunk The number of segments (as defined in the stream's segment index)
* that should be grouped into a single chunk.
* @param representations The representations to be considered by the source.
*/
public
DashMp4ChunkSource
(
DataSource
dataSource
,
FormatEvaluator
evaluator
,
int
numSegmentsPerChunk
,
Representation
...
representations
)
{
this
.
dataSource
=
dataSource
;
this
.
dataSource
=
dataSource
;
this
.
evaluator
=
evaluator
;
this
.
evaluator
=
evaluator
;
this
.
numSegmentsPerChunk
=
numSegmentsPerChunk
;
this
.
formats
=
new
Format
[
representations
.
length
];
this
.
formats
=
new
Format
[
representations
.
length
];
this
.
extractors
=
new
SparseArray
<
FragmentedMp4Extractor
>();
this
.
extractors
=
new
HashMap
<
String
,
FragmentedMp4Extractor
>();
this
.
representations
=
new
SparseArray
<
Representation
>();
this
.
segmentIndexes
=
new
HashMap
<
String
,
DashSegmentIndex
>();
this
.
representations
=
new
HashMap
<
String
,
Representation
>();
this
.
trackInfo
=
new
TrackInfo
(
representations
[
0
].
format
.
mimeType
,
this
.
trackInfo
=
new
TrackInfo
(
representations
[
0
].
format
.
mimeType
,
representations
[
0
].
periodDuration
*
1000
);
representations
[
0
].
periodDuration
Ms
*
1000
);
this
.
evaluation
=
new
Evaluation
();
this
.
evaluation
=
new
Evaluation
();
int
maxWidth
=
0
;
int
maxWidth
=
0
;
int
maxHeight
=
0
;
int
maxHeight
=
0
;
...
@@ -103,8 +82,12 @@ public class DashMp4ChunkSource implements ChunkSource {
...
@@ -103,8 +82,12 @@ public class DashMp4ChunkSource implements ChunkSource {
formats
[
i
]
=
representations
[
i
].
format
;
formats
[
i
]
=
representations
[
i
].
format
;
maxWidth
=
Math
.
max
(
formats
[
i
].
width
,
maxWidth
);
maxWidth
=
Math
.
max
(
formats
[
i
].
width
,
maxWidth
);
maxHeight
=
Math
.
max
(
formats
[
i
].
height
,
maxHeight
);
maxHeight
=
Math
.
max
(
formats
[
i
].
height
,
maxHeight
);
extractors
.
append
(
formats
[
i
].
id
,
new
FragmentedMp4Extractor
());
extractors
.
put
(
formats
[
i
].
id
,
new
FragmentedMp4Extractor
());
this
.
representations
.
put
(
formats
[
i
].
id
,
representations
[
i
]);
this
.
representations
.
put
(
formats
[
i
].
id
,
representations
[
i
]);
DashSegmentIndex
segmentIndex
=
representations
[
i
].
getIndex
();
if
(
segmentIndex
!=
null
)
{
segmentIndexes
.
put
(
formats
[
i
].
id
,
segmentIndex
);
}
}
}
this
.
maxWidth
=
maxWidth
;
this
.
maxWidth
=
maxWidth
;
this
.
maxHeight
=
maxHeight
;
this
.
maxHeight
=
maxHeight
;
...
@@ -129,7 +112,7 @@ public class DashMp4ChunkSource implements ChunkSource {
...
@@ -129,7 +112,7 @@ public class DashMp4ChunkSource implements ChunkSource {
}
}
@Override
@Override
public
void
disable
(
List
<
MediaChunk
>
queue
)
{
public
void
disable
(
List
<
?
extends
MediaChunk
>
queue
)
{
evaluator
.
disable
();
evaluator
.
disable
();
}
}
...
@@ -152,7 +135,7 @@ public class DashMp4ChunkSource implements ChunkSource {
...
@@ -152,7 +135,7 @@ public class DashMp4ChunkSource implements ChunkSource {
out
.
chunk
=
null
;
out
.
chunk
=
null
;
return
;
return
;
}
else
if
(
out
.
queueSize
==
queue
.
size
()
&&
out
.
chunk
!=
null
}
else
if
(
out
.
queueSize
==
queue
.
size
()
&&
out
.
chunk
!=
null
&&
out
.
chunk
.
format
.
id
==
selectedFormat
.
id
)
{
&&
out
.
chunk
.
format
.
id
.
equals
(
selectedFormat
.
id
)
)
{
// We already have a chunk, and the evaluation hasn't changed either the format or the size
// We already have a chunk, and the evaluation hasn't changed either the format or the size
// of the queue. Leave unchanged.
// of the queue. Leave unchanged.
return
;
return
;
...
@@ -160,29 +143,39 @@ public class DashMp4ChunkSource implements ChunkSource {
...
@@ -160,29 +143,39 @@ public class DashMp4ChunkSource implements ChunkSource {
Representation
selectedRepresentation
=
representations
.
get
(
selectedFormat
.
id
);
Representation
selectedRepresentation
=
representations
.
get
(
selectedFormat
.
id
);
FragmentedMp4Extractor
extractor
=
extractors
.
get
(
selectedRepresentation
.
format
.
id
);
FragmentedMp4Extractor
extractor
=
extractors
.
get
(
selectedRepresentation
.
format
.
id
);
RangedUri
pendingInitializationUri
=
null
;
RangedUri
pendingIndexUri
=
null
;
if
(
extractor
.
getTrack
()
==
null
)
{
if
(
extractor
.
getTrack
()
==
null
)
{
Chunk
initializationChunk
=
newInitializationChunk
(
selectedRepresentation
,
extractor
,
pendingInitializationUri
=
selectedRepresentation
.
getInitializationUri
();
dataSource
,
evaluation
.
trigger
);
}
if
(!
segmentIndexes
.
containsKey
(
selectedRepresentation
.
format
.
id
))
{
pendingIndexUri
=
selectedRepresentation
.
getIndexUri
();
}
if
(
pendingInitializationUri
!=
null
||
pendingIndexUri
!=
null
)
{
// We have initialization and/or index requests to make.
Chunk
initializationChunk
=
newInitializationChunk
(
pendingInitializationUri
,
pendingIndexUri
,
selectedRepresentation
,
extractor
,
dataSource
,
evaluation
.
trigger
);
lastChunkWasInitialization
=
true
;
lastChunkWasInitialization
=
true
;
out
.
chunk
=
initializationChunk
;
out
.
chunk
=
initializationChunk
;
return
;
return
;
}
}
int
nextIndex
;
int
nextSegmentNum
;
DashSegmentIndex
segmentIndex
=
segmentIndexes
.
get
(
selectedRepresentation
.
format
.
id
);
if
(
queue
.
isEmpty
())
{
if
(
queue
.
isEmpty
())
{
nextIndex
=
Arrays
.
binarySearch
(
extractor
.
getSegmentIndex
().
timesUs
,
seekPositionUs
);
nextSegmentNum
=
segmentIndex
.
getSegmentNum
(
seekPositionUs
);
nextIndex
=
nextIndex
<
0
?
-
nextIndex
-
2
:
nextIndex
;
}
else
{
}
else
{
next
Index
=
queue
.
get
(
out
.
queueSize
-
1
).
nextChunkIndex
;
next
SegmentNum
=
queue
.
get
(
out
.
queueSize
-
1
).
nextChunkIndex
;
}
}
if
(
next
Index
==
-
1
)
{
if
(
next
SegmentNum
==
-
1
)
{
out
.
chunk
=
null
;
out
.
chunk
=
null
;
return
;
return
;
}
}
Chunk
nextMediaChunk
=
newMediaChunk
(
selectedRepresentation
,
extractor
,
dataSource
,
Chunk
nextMediaChunk
=
newMediaChunk
(
selectedRepresentation
,
segmentIndex
,
extractor
,
extractor
.
getSegmentIndex
(),
nextIndex
,
evaluation
.
trigger
,
numSegmentsPerChunk
);
dataSource
,
nextSegmentNum
,
evaluation
.
trigger
);
lastChunkWasInitialization
=
false
;
lastChunkWasInitialization
=
false
;
out
.
chunk
=
nextMediaChunk
;
out
.
chunk
=
nextMediaChunk
;
}
}
...
@@ -192,75 +185,80 @@ public class DashMp4ChunkSource implements ChunkSource {
...
@@ -192,75 +185,80 @@ public class DashMp4ChunkSource implements ChunkSource {
return
null
;
return
null
;
}
}
private
static
Chunk
newInitializationChunk
(
Representation
representation
,
@Override
FragmentedMp4Extractor
extractor
,
DataSource
dataSource
,
int
trigger
)
{
public
void
onChunkLoadError
(
Chunk
chunk
,
Exception
e
)
{
DataSpec
dataSpec
=
new
DataSpec
(
representation
.
uri
,
0
,
representation
.
indexEnd
+
1
,
// Do nothing.
representation
.
getCacheKey
());
return
new
InitializationMp4Loadable
(
dataSource
,
dataSpec
,
trigger
,
extractor
,
representation
);
}
}
private
static
Chunk
newMediaChunk
(
Representation
representation
,
private
Chunk
newInitializationChunk
(
RangedUri
initializationUri
,
RangedUri
indexUri
,
FragmentedMp4Extractor
extractor
,
DataSource
dataSource
,
SegmentIndex
sidx
,
int
index
,
Representation
representation
,
FragmentedMp4Extractor
extractor
,
DataSource
dataSource
,
int
trigger
,
int
numSegmentsPerChunk
)
{
int
trigger
)
{
int
expectedExtractorResult
=
FragmentedMp4Extractor
.
RESULT_END_OF_STREAM
;
// Computes the segments to included in the next fetch.
long
indexAnchor
=
0
;
int
numSegmentsToFetch
=
Math
.
min
(
numSegmentsPerChunk
,
sidx
.
length
-
index
);
RangedUri
requestUri
;
int
lastSegmentInChunk
=
index
+
numSegmentsToFetch
-
1
;
if
(
initializationUri
!=
null
)
{
int
nextIndex
=
lastSegmentInChunk
==
sidx
.
length
-
1
?
-
1
:
lastSegmentInChunk
+
1
;
// It's common for initialization and index data to be stored adjacently. Attempt to merge
// the two requests together to request both at once.
long
startTimeUs
=
sidx
.
timesUs
[
index
];
expectedExtractorResult
|=
FragmentedMp4Extractor
.
RESULT_READ_MOOV
;
requestUri
=
initializationUri
.
attemptMerge
(
indexUri
);
// Compute the end time, prefer to use next segment start time if there is a next segment.
if
(
requestUri
!=
null
)
{
long
endTimeUs
=
nextIndex
==
-
1
?
expectedExtractorResult
|=
FragmentedMp4Extractor
.
RESULT_READ_SIDX
;
sidx
.
timesUs
[
lastSegmentInChunk
]
+
sidx
.
durationsUs
[
lastSegmentInChunk
]
:
indexAnchor
=
indexUri
.
start
+
indexUri
.
length
;
sidx
.
timesUs
[
nextIndex
];
}
else
{
requestUri
=
initializationUri
;
long
offset
=
(
int
)
representation
.
indexEnd
+
1
+
sidx
.
offsets
[
index
];
}
}
else
{
// Compute combined segments byte length.
requestUri
=
indexUri
;
long
size
=
0
;
indexAnchor
=
indexUri
.
start
+
indexUri
.
length
;
for
(
int
i
=
index
;
i
<=
lastSegmentInChunk
;
i
++)
{
expectedExtractorResult
|=
FragmentedMp4Extractor
.
RESULT_READ_SIDX
;
size
+=
sidx
.
sizes
[
i
];
}
DataSpec
dataSpec
=
new
DataSpec
(
requestUri
.
getUri
(),
requestUri
.
start
,
requestUri
.
length
,
representation
.
getCacheKey
());
return
new
InitializationMp4Loadable
(
dataSource
,
dataSpec
,
trigger
,
representation
.
format
,
extractor
,
expectedExtractorResult
,
indexAnchor
);
}
}
DataSpec
dataSpec
=
new
DataSpec
(
representation
.
uri
,
offset
,
size
,
private
Chunk
newMediaChunk
(
Representation
representation
,
DashSegmentIndex
segmentIndex
,
FragmentedMp4Extractor
extractor
,
DataSource
dataSource
,
int
segmentNum
,
int
trigger
)
{
int
lastSegmentNum
=
segmentIndex
.
getLastSegmentNum
();
int
nextSegmentNum
=
segmentNum
==
lastSegmentNum
?
-
1
:
segmentNum
+
1
;
long
startTimeUs
=
segmentIndex
.
getTimeUs
(
segmentNum
);
long
endTimeUs
=
segmentNum
<
lastSegmentNum
?
segmentIndex
.
getTimeUs
(
segmentNum
+
1
)
:
startTimeUs
+
segmentIndex
.
getDurationUs
(
segmentNum
);
RangedUri
segmentUri
=
segmentIndex
.
getSegmentUrl
(
segmentNum
);
DataSpec
dataSpec
=
new
DataSpec
(
segmentUri
.
getUri
(),
segmentUri
.
start
,
segmentUri
.
length
,
representation
.
getCacheKey
());
representation
.
getCacheKey
());
return
new
Mp4MediaChunk
(
dataSource
,
dataSpec
,
representation
.
format
,
trigger
,
extractor
,
return
new
Mp4MediaChunk
(
dataSource
,
dataSpec
,
representation
.
format
,
trigger
,
startTimeUs
,
startTimeUs
,
endTimeUs
,
0
,
nextIndex
);
endTimeUs
,
nextSegmentNum
,
extractor
,
false
,
0
);
}
}
private
static
class
InitializationMp4Loadable
extends
Chunk
{
private
class
InitializationMp4Loadable
extends
Chunk
{
private
final
Representation
representation
;
private
final
FragmentedMp4Extractor
extractor
;
private
final
FragmentedMp4Extractor
extractor
;
private
final
int
expectedExtractorResult
;
private
final
long
indexAnchor
;
private
final
Uri
uri
;
public
InitializationMp4Loadable
(
DataSource
dataSource
,
DataSpec
dataSpec
,
int
trigger
,
public
InitializationMp4Loadable
(
DataSource
dataSource
,
DataSpec
dataSpec
,
int
trigger
,
FragmentedMp4Extractor
extractor
,
Representation
representation
)
{
Format
format
,
FragmentedMp4Extractor
extractor
,
int
expectedExtractorResult
,
super
(
dataSource
,
dataSpec
,
representation
.
format
,
trigger
);
long
indexAnchor
)
{
super
(
dataSource
,
dataSpec
,
format
,
trigger
);
this
.
extractor
=
extractor
;
this
.
extractor
=
extractor
;
this
.
representation
=
representation
;
this
.
expectedExtractorResult
=
expectedExtractorResult
;
this
.
indexAnchor
=
indexAnchor
;
this
.
uri
=
dataSpec
.
uri
;
}
}
@Override
@Override
protected
void
consumeStream
(
NonBlockingInputStream
stream
)
throws
IOException
{
protected
void
consumeStream
(
NonBlockingInputStream
stream
)
throws
IOException
{
int
result
=
extractor
.
read
(
stream
,
null
);
int
result
=
extractor
.
read
(
stream
,
null
);
if
(
result
!=
EXPECTED_INITIALIZATION_RESULT
)
{
if
(
result
!=
expectedExtractorResult
)
{
throw
new
ParserException
(
"Invalid initialization data"
);
throw
new
ParserException
(
"Invalid extractor result. Expected "
}
+
expectedExtractorResult
+
", got "
+
result
);
validateSegmentIndex
(
extractor
.
getSegmentIndex
());
}
private
void
validateSegmentIndex
(
SegmentIndex
segmentIndex
)
{
long
expectedIndexLen
=
representation
.
indexEnd
-
representation
.
indexStart
+
1
;
if
(
segmentIndex
.
sizeBytes
!=
expectedIndexLen
)
{
Log
.
w
(
TAG
,
"Sidx length mismatch: sidxLen = "
+
segmentIndex
.
sizeBytes
+
", ExpectedLen = "
+
expectedIndexLen
);
}
}
long
sidxContentLength
=
segmentIndex
.
offsets
[
segmentIndex
.
length
-
1
]
+
if
((
result
&
FragmentedMp4Extractor
.
RESULT_READ_SIDX
)
!=
0
)
{
segmentIndex
.
sizes
[
segmentIndex
.
length
-
1
]
+
representation
.
indexEnd
+
1
;
segmentIndexes
.
put
(
format
.
id
,
if
(
sidxContentLength
!=
representation
.
contentLength
)
{
new
DashWrappingSegmentIndex
(
extractor
.
getSegmentIndex
(),
uri
,
indexAnchor
));
Log
.
w
(
TAG
,
"ContentLength mismatch: Actual = "
+
sidxContentLength
+
", Expected = "
+
representation
.
contentLength
);
}
}
}
}
...
...
library/src/main/java/com/google/android/exoplayer/dash/DashWebmChunkSource.java
View file @
4228f2cf
...
@@ -27,18 +27,19 @@ import com.google.android.exoplayer.chunk.FormatEvaluator;
...
@@ -27,18 +27,19 @@ import com.google.android.exoplayer.chunk.FormatEvaluator;
import
com.google.android.exoplayer.chunk.FormatEvaluator.Evaluation
;
import
com.google.android.exoplayer.chunk.FormatEvaluator.Evaluation
;
import
com.google.android.exoplayer.chunk.MediaChunk
;
import
com.google.android.exoplayer.chunk.MediaChunk
;
import
com.google.android.exoplayer.chunk.WebmMediaChunk
;
import
com.google.android.exoplayer.chunk.WebmMediaChunk
;
import
com.google.android.exoplayer.dash.mpd.RangedUri
;
import
com.google.android.exoplayer.dash.mpd.Representation
;
import
com.google.android.exoplayer.dash.mpd.Representation
;
import
com.google.android.exoplayer.parser.
SegmentIndex
;
import
com.google.android.exoplayer.parser.
webm.DefaultWebmExtractor
;
import
com.google.android.exoplayer.parser.webm.WebmExtractor
;
import
com.google.android.exoplayer.parser.webm.WebmExtractor
;
import
com.google.android.exoplayer.upstream.DataSource
;
import
com.google.android.exoplayer.upstream.DataSource
;
import
com.google.android.exoplayer.upstream.DataSpec
;
import
com.google.android.exoplayer.upstream.DataSpec
;
import
com.google.android.exoplayer.upstream.NonBlockingInputStream
;
import
com.google.android.exoplayer.upstream.NonBlockingInputStream
;
import
android.util.Log
;
import
android.net.Uri
;
import
android.util.SparseArray
;
import
java.io.IOException
;
import
java.io.IOException
;
import
java.util.Arrays
;
import
java.util.Arrays
;
import
java.util.HashMap
;
import
java.util.List
;
import
java.util.List
;
/**
/**
...
@@ -46,37 +47,30 @@ import java.util.List;
...
@@ -46,37 +47,30 @@ import java.util.List;
*/
*/
public
class
DashWebmChunkSource
implements
ChunkSource
{
public
class
DashWebmChunkSource
implements
ChunkSource
{
private
static
final
String
TAG
=
"DashWebmChunkSource"
;
private
final
TrackInfo
trackInfo
;
private
final
TrackInfo
trackInfo
;
private
final
DataSource
dataSource
;
private
final
DataSource
dataSource
;
private
final
FormatEvaluator
evaluator
;
private
final
FormatEvaluator
evaluator
;
private
final
Evaluation
evaluation
;
private
final
Evaluation
evaluation
;
private
final
int
maxWidth
;
private
final
int
maxWidth
;
private
final
int
maxHeight
;
private
final
int
maxHeight
;
private
final
int
numSegmentsPerChunk
;
private
final
Format
[]
formats
;
private
final
Format
[]
formats
;
private
final
SparseArray
<
Representation
>
representations
;
private
final
HashMap
<
String
,
Representation
>
representations
;
private
final
SparseArray
<
WebmExtractor
>
extractors
;
private
final
HashMap
<
String
,
WebmExtractor
>
extractors
;
private
final
HashMap
<
String
,
DashSegmentIndex
>
segmentIndexes
;
private
boolean
lastChunkWasInitialization
;
private
boolean
lastChunkWasInitialization
;
public
DashWebmChunkSource
(
DataSource
dataSource
,
FormatEvaluator
evaluator
,
Representation
...
representations
)
{
this
(
dataSource
,
evaluator
,
1
,
representations
);
}
public
DashWebmChunkSource
(
DataSource
dataSource
,
FormatEvaluator
evaluator
,
public
DashWebmChunkSource
(
DataSource
dataSource
,
FormatEvaluator
evaluator
,
int
numSegmentsPerChunk
,
Representation
...
representations
)
{
Representation
...
representations
)
{
this
.
dataSource
=
dataSource
;
this
.
dataSource
=
dataSource
;
this
.
evaluator
=
evaluator
;
this
.
evaluator
=
evaluator
;
this
.
numSegmentsPerChunk
=
numSegmentsPerChunk
;
this
.
formats
=
new
Format
[
representations
.
length
];
this
.
formats
=
new
Format
[
representations
.
length
];
this
.
extractors
=
new
SparseArray
<
WebmExtractor
>();
this
.
extractors
=
new
HashMap
<
String
,
WebmExtractor
>();
this
.
representations
=
new
SparseArray
<
Representation
>();
this
.
segmentIndexes
=
new
HashMap
<
String
,
DashSegmentIndex
>();
this
.
representations
=
new
HashMap
<
String
,
Representation
>();
this
.
trackInfo
=
new
TrackInfo
(
this
.
trackInfo
=
new
TrackInfo
(
representations
[
0
].
format
.
mimeType
,
representations
[
0
].
periodDuration
*
1000
);
representations
[
0
].
format
.
mimeType
,
representations
[
0
].
periodDuration
Ms
*
1000
);
this
.
evaluation
=
new
Evaluation
();
this
.
evaluation
=
new
Evaluation
();
int
maxWidth
=
0
;
int
maxWidth
=
0
;
int
maxHeight
=
0
;
int
maxHeight
=
0
;
...
@@ -84,8 +78,12 @@ public class DashWebmChunkSource implements ChunkSource {
...
@@ -84,8 +78,12 @@ public class DashWebmChunkSource implements ChunkSource {
formats
[
i
]
=
representations
[
i
].
format
;
formats
[
i
]
=
representations
[
i
].
format
;
maxWidth
=
Math
.
max
(
formats
[
i
].
width
,
maxWidth
);
maxWidth
=
Math
.
max
(
formats
[
i
].
width
,
maxWidth
);
maxHeight
=
Math
.
max
(
formats
[
i
].
height
,
maxHeight
);
maxHeight
=
Math
.
max
(
formats
[
i
].
height
,
maxHeight
);
extractors
.
append
(
formats
[
i
].
id
,
new
WebmExtractor
());
extractors
.
put
(
formats
[
i
].
id
,
new
Default
WebmExtractor
());
this
.
representations
.
put
(
formats
[
i
].
id
,
representations
[
i
]);
this
.
representations
.
put
(
formats
[
i
].
id
,
representations
[
i
]);
DashSegmentIndex
segmentIndex
=
representations
[
i
].
getIndex
();
if
(
segmentIndex
!=
null
)
{
segmentIndexes
.
put
(
formats
[
i
].
id
,
segmentIndex
);
}
}
}
this
.
maxWidth
=
maxWidth
;
this
.
maxWidth
=
maxWidth
;
this
.
maxHeight
=
maxHeight
;
this
.
maxHeight
=
maxHeight
;
...
@@ -110,7 +108,7 @@ public class DashWebmChunkSource implements ChunkSource {
...
@@ -110,7 +108,7 @@ public class DashWebmChunkSource implements ChunkSource {
}
}
@Override
@Override
public
void
disable
(
List
<
MediaChunk
>
queue
)
{
public
void
disable
(
List
<
?
extends
MediaChunk
>
queue
)
{
evaluator
.
disable
();
evaluator
.
disable
();
}
}
...
@@ -133,7 +131,7 @@ public class DashWebmChunkSource implements ChunkSource {
...
@@ -133,7 +131,7 @@ public class DashWebmChunkSource implements ChunkSource {
out
.
chunk
=
null
;
out
.
chunk
=
null
;
return
;
return
;
}
else
if
(
out
.
queueSize
==
queue
.
size
()
&&
out
.
chunk
!=
null
}
else
if
(
out
.
queueSize
==
queue
.
size
()
&&
out
.
chunk
!=
null
&&
out
.
chunk
.
format
.
id
==
selectedFormat
.
id
)
{
&&
out
.
chunk
.
format
.
id
.
equals
(
selectedFormat
.
id
)
)
{
// We already have a chunk, and the evaluation hasn't changed either the format or the size
// We already have a chunk, and the evaluation hasn't changed either the format or the size
// of the queue. Leave unchanged.
// of the queue. Leave unchanged.
return
;
return
;
...
@@ -141,29 +139,34 @@ public class DashWebmChunkSource implements ChunkSource {
...
@@ -141,29 +139,34 @@ public class DashWebmChunkSource implements ChunkSource {
Representation
selectedRepresentation
=
representations
.
get
(
selectedFormat
.
id
);
Representation
selectedRepresentation
=
representations
.
get
(
selectedFormat
.
id
);
WebmExtractor
extractor
=
extractors
.
get
(
selectedRepresentation
.
format
.
id
);
WebmExtractor
extractor
=
extractors
.
get
(
selectedRepresentation
.
format
.
id
);
if
(!
extractor
.
isPrepared
())
{
if
(!
extractor
.
isPrepared
())
{
Chunk
initializationChunk
=
newInitializationChunk
(
selectedRepresentation
,
extractor
,
// TODO: This code forces cues to exist and to immediately follow the initialization
dataSource
,
evaluation
.
trigger
);
// data. Webm extractor should be generalized to allow cues to be optional. See [redacted].
RangedUri
initializationUri
=
selectedRepresentation
.
getInitializationUri
().
attemptMerge
(
selectedRepresentation
.
getIndexUri
());
Chunk
initializationChunk
=
newInitializationChunk
(
initializationUri
,
selectedRepresentation
,
extractor
,
dataSource
,
evaluation
.
trigger
);
lastChunkWasInitialization
=
true
;
lastChunkWasInitialization
=
true
;
out
.
chunk
=
initializationChunk
;
out
.
chunk
=
initializationChunk
;
return
;
return
;
}
}
int
nextIndex
;
int
nextSegmentNum
;
DashSegmentIndex
segmentIndex
=
segmentIndexes
.
get
(
selectedRepresentation
.
format
.
id
);
if
(
queue
.
isEmpty
())
{
if
(
queue
.
isEmpty
())
{
nextIndex
=
Arrays
.
binarySearch
(
extractor
.
getCues
().
timesUs
,
seekPositionUs
);
nextSegmentNum
=
segmentIndex
.
getSegmentNum
(
seekPositionUs
);
nextIndex
=
nextIndex
<
0
?
-
nextIndex
-
2
:
nextIndex
;
}
else
{
}
else
{
next
Index
=
queue
.
get
(
out
.
queueSize
-
1
).
nextChunkIndex
;
next
SegmentNum
=
queue
.
get
(
out
.
queueSize
-
1
).
nextChunkIndex
;
}
}
if
(
next
Index
==
-
1
)
{
if
(
next
SegmentNum
==
-
1
)
{
out
.
chunk
=
null
;
out
.
chunk
=
null
;
return
;
return
;
}
}
Chunk
nextMediaChunk
=
newMediaChunk
(
selectedRepresentation
,
extractor
,
dataSource
,
Chunk
nextMediaChunk
=
newMediaChunk
(
selectedRepresentation
,
segmentIndex
,
extractor
,
extractor
.
getCues
(),
nextIndex
,
evaluation
.
trigger
,
numSegmentsPerChunk
);
dataSource
,
nextSegmentNum
,
evaluation
.
trigger
);
lastChunkWasInitialization
=
false
;
lastChunkWasInitialization
=
false
;
out
.
chunk
=
nextMediaChunk
;
out
.
chunk
=
nextMediaChunk
;
}
}
...
@@ -173,53 +176,43 @@ public class DashWebmChunkSource implements ChunkSource {
...
@@ -173,53 +176,43 @@ public class DashWebmChunkSource implements ChunkSource {
return
null
;
return
null
;
}
}
private
static
Chunk
newInitializationChunk
(
Representation
representation
,
@Override
WebmExtractor
extractor
,
DataSource
dataSource
,
int
trigger
)
{
public
void
onChunkLoadError
(
Chunk
chunk
,
Exception
e
)
{
DataSpec
dataSpec
=
new
DataSpec
(
representation
.
uri
,
0
,
representation
.
indexEnd
+
1
,
// Do nothing.
representation
.
getCacheKey
());
return
new
InitializationWebmLoadable
(
dataSource
,
dataSpec
,
trigger
,
extractor
,
representation
);
}
private
static
Chunk
newMediaChunk
(
Representation
representation
,
WebmExtractor
extractor
,
DataSource
dataSource
,
SegmentIndex
cues
,
int
index
,
int
trigger
,
int
numSegmentsPerChunk
)
{
// Computes the segments to included in the next fetch.
int
numSegmentsToFetch
=
Math
.
min
(
numSegmentsPerChunk
,
cues
.
length
-
index
);
int
lastSegmentInChunk
=
index
+
numSegmentsToFetch
-
1
;
int
nextIndex
=
lastSegmentInChunk
==
cues
.
length
-
1
?
-
1
:
lastSegmentInChunk
+
1
;
long
startTimeUs
=
cues
.
timesUs
[
index
];
// Compute the end time, prefer to use next segment start time if there is a next segment.
long
endTimeUs
=
nextIndex
==
-
1
?
cues
.
timesUs
[
lastSegmentInChunk
]
+
cues
.
durationsUs
[
lastSegmentInChunk
]
:
cues
.
timesUs
[
nextIndex
];
long
offset
=
cues
.
offsets
[
index
];
// Compute combined segments byte length.
long
size
=
0
;
for
(
int
i
=
index
;
i
<=
lastSegmentInChunk
;
i
++)
{
size
+=
cues
.
sizes
[
i
];
}
}
DataSpec
dataSpec
=
new
DataSpec
(
representation
.
uri
,
offset
,
size
,
private
Chunk
newInitializationChunk
(
RangedUri
initializationUri
,
Representation
representation
,
WebmExtractor
extractor
,
DataSource
dataSource
,
int
trigger
)
{
DataSpec
dataSpec
=
new
DataSpec
(
initializationUri
.
getUri
(),
initializationUri
.
start
,
initializationUri
.
length
,
representation
.
getCacheKey
());
return
new
InitializationWebmLoadable
(
dataSource
,
dataSpec
,
trigger
,
representation
.
format
,
extractor
);
}
private
Chunk
newMediaChunk
(
Representation
representation
,
DashSegmentIndex
segmentIndex
,
WebmExtractor
extractor
,
DataSource
dataSource
,
int
segmentNum
,
int
trigger
)
{
int
lastSegmentNum
=
segmentIndex
.
getLastSegmentNum
();
int
nextSegmentNum
=
segmentNum
==
lastSegmentNum
?
-
1
:
segmentNum
+
1
;
long
startTimeUs
=
segmentIndex
.
getTimeUs
(
segmentNum
);
long
endTimeUs
=
segmentNum
<
lastSegmentNum
?
segmentIndex
.
getTimeUs
(
segmentNum
+
1
)
:
startTimeUs
+
segmentIndex
.
getDurationUs
(
segmentNum
);
RangedUri
segmentUri
=
segmentIndex
.
getSegmentUrl
(
segmentNum
);
DataSpec
dataSpec
=
new
DataSpec
(
segmentUri
.
getUri
(),
segmentUri
.
start
,
segmentUri
.
length
,
representation
.
getCacheKey
());
representation
.
getCacheKey
());
return
new
WebmMediaChunk
(
dataSource
,
dataSpec
,
representation
.
format
,
trigger
,
extractor
,
return
new
WebmMediaChunk
(
dataSource
,
dataSpec
,
representation
.
format
,
trigger
,
extractor
,
startTimeUs
,
endTimeUs
,
next
Index
);
startTimeUs
,
endTimeUs
,
next
SegmentNum
);
}
}
private
static
class
InitializationWebmLoadable
extends
Chunk
{
private
class
InitializationWebmLoadable
extends
Chunk
{
private
final
Representation
representation
;
private
final
WebmExtractor
extractor
;
private
final
WebmExtractor
extractor
;
private
final
Uri
uri
;
public
InitializationWebmLoadable
(
DataSource
dataSource
,
DataSpec
dataSpec
,
int
trigger
,
public
InitializationWebmLoadable
(
DataSource
dataSource
,
DataSpec
dataSpec
,
int
trigger
,
WebmExtractor
extractor
,
Representation
representation
)
{
Format
format
,
WebmExtractor
extractor
)
{
super
(
dataSource
,
dataSpec
,
representation
.
format
,
trigger
);
super
(
dataSource
,
dataSpec
,
format
,
trigger
);
this
.
extractor
=
extractor
;
this
.
extractor
=
extractor
;
this
.
representation
=
representation
;
this
.
uri
=
dataSpec
.
uri
;
}
}
@Override
@Override
...
@@ -228,22 +221,7 @@ public class DashWebmChunkSource implements ChunkSource {
...
@@ -228,22 +221,7 @@ public class DashWebmChunkSource implements ChunkSource {
if
(!
extractor
.
isPrepared
())
{
if
(!
extractor
.
isPrepared
())
{
throw
new
ParserException
(
"Invalid initialization data"
);
throw
new
ParserException
(
"Invalid initialization data"
);
}
}
validateCues
(
extractor
.
getCues
());
segmentIndexes
.
put
(
format
.
id
,
new
DashWrappingSegmentIndex
(
extractor
.
getCues
(),
uri
,
0
));
}
private
void
validateCues
(
SegmentIndex
cues
)
{
long
expectedSizeBytes
=
representation
.
indexEnd
-
representation
.
indexStart
+
1
;
if
(
cues
.
sizeBytes
!=
expectedSizeBytes
)
{
Log
.
w
(
TAG
,
"Cues length mismatch: got "
+
cues
.
sizeBytes
+
" but expected "
+
expectedSizeBytes
);
}
long
expectedContentLength
=
cues
.
offsets
[
cues
.
length
-
1
]
+
cues
.
sizes
[
cues
.
length
-
1
]
+
representation
.
indexEnd
+
1
;
if
(
representation
.
contentLength
>
0
&&
expectedContentLength
!=
representation
.
contentLength
)
{
Log
.
w
(
TAG
,
"ContentLength mismatch: got "
+
expectedContentLength
+
" but expected "
+
representation
.
contentLength
);
}
}
}
}
}
...
...
library/src/main/java/com/google/android/exoplayer/dash/mpd/MediaPresentationDescriptionFetcher.java
View file @
4228f2cf
...
@@ -18,6 +18,8 @@ package com.google.android.exoplayer.dash.mpd;
...
@@ -18,6 +18,8 @@ package com.google.android.exoplayer.dash.mpd;
import
com.google.android.exoplayer.ParserException
;
import
com.google.android.exoplayer.ParserException
;
import
com.google.android.exoplayer.util.ManifestFetcher
;
import
com.google.android.exoplayer.util.ManifestFetcher
;
import
android.net.Uri
;
import
org.xmlpull.v1.XmlPullParserException
;
import
org.xmlpull.v1.XmlPullParserException
;
import
java.io.IOException
;
import
java.io.IOException
;
...
@@ -57,9 +59,9 @@ public final class MediaPresentationDescriptionFetcher extends
...
@@ -57,9 +59,9 @@ public final class MediaPresentationDescriptionFetcher extends
@Override
@Override
protected
MediaPresentationDescription
parse
(
InputStream
stream
,
String
inputEncoding
,
protected
MediaPresentationDescription
parse
(
InputStream
stream
,
String
inputEncoding
,
String
contentId
)
throws
IOException
,
ParserException
{
String
contentId
,
Uri
baseUrl
)
throws
IOException
,
ParserException
{
try
{
try
{
return
parser
.
parseMediaPresentationDescription
(
stream
,
inputEncoding
,
contentId
);
return
parser
.
parseMediaPresentationDescription
(
stream
,
inputEncoding
,
contentId
,
baseUrl
);
}
catch
(
XmlPullParserException
e
)
{
}
catch
(
XmlPullParserException
e
)
{
throw
new
ParserException
(
e
);
throw
new
ParserException
(
e
);
}
}
...
...
library/src/main/java/com/google/android/exoplayer/dash/mpd/MediaPresentationDescriptionParser.java
View file @
4228f2cf
...
@@ -17,11 +17,15 @@ package com.google.android.exoplayer.dash.mpd;
...
@@ -17,11 +17,15 @@ package com.google.android.exoplayer.dash.mpd;
import
com.google.android.exoplayer.ParserException
;
import
com.google.android.exoplayer.ParserException
;
import
com.google.android.exoplayer.chunk.Format
;
import
com.google.android.exoplayer.chunk.Format
;
import
com.google.android.exoplayer.upstream.DataSpec
;
import
com.google.android.exoplayer.dash.mpd.SegmentBase.SegmentList
;
import
com.google.android.exoplayer.dash.mpd.SegmentBase.SegmentTemplate
;
import
com.google.android.exoplayer.dash.mpd.SegmentBase.SegmentTimelineElement
;
import
com.google.android.exoplayer.dash.mpd.SegmentBase.SingleSegmentBase
;
import
com.google.android.exoplayer.util.Assertions
;
import
com.google.android.exoplayer.util.MimeTypes
;
import
com.google.android.exoplayer.util.MimeTypes
;
import
android.net.Uri
;
import
android.net.Uri
;
import
android.
util.Log
;
import
android.
text.TextUtils
;
import
org.xml.sax.helpers.DefaultHandler
;
import
org.xml.sax.helpers.DefaultHandler
;
import
org.xmlpull.v1.XmlPullParser
;
import
org.xmlpull.v1.XmlPullParser
;
...
@@ -38,15 +42,8 @@ import java.util.regex.Pattern;
...
@@ -38,15 +42,8 @@ import java.util.regex.Pattern;
/**
/**
* A parser of media presentation description files.
* A parser of media presentation description files.
*/
*/
/*
* TODO: Parse representation base attributes at multiple levels, and normalize the resulting
* datastructure.
* TODO: Decide how best to represent missing integer/double/long attributes.
*/
public
class
MediaPresentationDescriptionParser
extends
DefaultHandler
{
public
class
MediaPresentationDescriptionParser
extends
DefaultHandler
{
private
static
final
String
TAG
=
"MediaPresentationDescriptionParser"
;
// Note: Does not support the date part of ISO 8601
// Note: Does not support the date part of ISO 8601
private
static
final
Pattern
DURATION
=
private
static
final
Pattern
DURATION
=
Pattern
.
compile
(
"^PT(([0-9]*)H)?(([0-9]*)M)?(([0-9.]*)S)?$"
);
Pattern
.
compile
(
"^PT(([0-9]*)H)?(([0-9]*)M)?(([0-9.]*)S)?$"
);
...
@@ -61,20 +58,23 @@ public class MediaPresentationDescriptionParser extends DefaultHandler {
...
@@ -61,20 +58,23 @@ public class MediaPresentationDescriptionParser extends DefaultHandler {
}
}
}
}
// MPD parsing.
/**
/**
* Parses a manifest from the provided {@link InputStream}.
* Parses a manifest from the provided {@link InputStream}.
*
*
* @param inputStream The stream from which to parse the manifest.
* @param inputStream The stream from which to parse the manifest.
* @param inputEncoding The encoding of the input.
* @param inputEncoding The encoding of the input.
* @param contentId The content id of the media.
* @param contentId The content id of the media.
* @param baseUrl The url that any relative urls defined within the manifest are relative to.
* @return The parsed manifest.
* @return The parsed manifest.
* @throws IOException If a problem occurred reading from the stream.
* @throws IOException If a problem occurred reading from the stream.
* @throws XmlPullParserException If a problem occurred parsing the stream as xml.
* @throws XmlPullParserException If a problem occurred parsing the stream as xml.
* @throws ParserException If a problem occurred parsing the xml as a DASH mpd.
* @throws ParserException If a problem occurred parsing the xml as a DASH mpd.
*/
*/
public
MediaPresentationDescription
parseMediaPresentationDescription
(
InputStream
inputStream
,
public
MediaPresentationDescription
parseMediaPresentationDescription
(
InputStream
inputStream
,
String
inputEncoding
,
String
contentId
)
throws
XmlPullParserException
,
IO
Exception
,
String
inputEncoding
,
String
contentId
,
Uri
baseUrl
)
throws
XmlPullParser
Exception
,
ParserException
{
IOException
,
ParserException
{
XmlPullParser
xpp
=
xmlParserFactory
.
newPullParser
();
XmlPullParser
xpp
=
xmlParserFactory
.
newPullParser
();
xpp
.
setInput
(
inputStream
,
inputEncoding
);
xpp
.
setInput
(
inputStream
,
inputEncoding
);
int
eventType
=
xpp
.
next
();
int
eventType
=
xpp
.
next
();
...
@@ -82,123 +82,139 @@ public class MediaPresentationDescriptionParser extends DefaultHandler {
...
@@ -82,123 +82,139 @@ public class MediaPresentationDescriptionParser extends DefaultHandler {
throw
new
ParserException
(
throw
new
ParserException
(
"inputStream does not contain a valid media presentation description"
);
"inputStream does not contain a valid media presentation description"
);
}
}
return
parseMediaPresentationDescription
(
xpp
,
contentId
);
return
parseMediaPresentationDescription
(
xpp
,
contentId
,
baseUrl
);
}
}
private
MediaPresentationDescription
parseMediaPresentationDescription
(
XmlPullParser
xpp
,
private
MediaPresentationDescription
parseMediaPresentationDescription
(
XmlPullParser
xpp
,
String
contentId
)
throws
XmlPullParserException
,
IOException
{
String
contentId
,
Uri
baseUrl
)
throws
XmlPullParserException
,
IOException
{
long
duration
=
parseDurationMs
(
xpp
,
"mediaPresentationDuration"
);
long
duration
Ms
=
parseDurationMs
(
xpp
,
"mediaPresentationDuration"
);
long
minBufferTime
=
parseDurationMs
(
xpp
,
"minBufferTime"
);
long
minBufferTime
Ms
=
parseDurationMs
(
xpp
,
"minBufferTime"
);
String
typeString
=
xpp
.
getAttributeValue
(
null
,
"type"
);
String
typeString
=
xpp
.
getAttributeValue
(
null
,
"type"
);
boolean
dynamic
=
(
typeString
!=
null
)
?
typeString
.
equals
(
"dynamic"
)
:
false
;
boolean
dynamic
=
(
typeString
!=
null
)
?
typeString
.
equals
(
"dynamic"
)
:
false
;
long
minUpdateTime
=
(
dynamic
)
?
parseDurationMs
(
xpp
,
"minimumUpdatePeriod"
,
-
1
)
:
-
1
;
long
minUpdateTime
Ms
=
(
dynamic
)
?
parseDurationMs
(
xpp
,
"minimumUpdatePeriod"
,
-
1
)
:
-
1
;
List
<
Period
>
periods
=
new
ArrayList
<
Period
>();
List
<
Period
>
periods
=
new
ArrayList
<
Period
>();
do
{
do
{
xpp
.
next
();
xpp
.
next
();
if
(
isStartTag
(
xpp
,
"Period"
))
{
if
(
isStartTag
(
xpp
,
"BaseURL"
))
{
periods
.
add
(
parsePeriod
(
xpp
,
contentId
,
duration
));
baseUrl
=
parseBaseUrl
(
xpp
,
baseUrl
);
}
else
if
(
isStartTag
(
xpp
,
"Period"
))
{
periods
.
add
(
parsePeriod
(
xpp
,
contentId
,
baseUrl
,
durationMs
));
}
}
}
while
(!
isEndTag
(
xpp
,
"MPD"
));
}
while
(!
isEndTag
(
xpp
,
"MPD"
));
return
new
MediaPresentationDescription
(
duration
,
minBufferTime
,
dynamic
,
minUpdateTime
,
return
new
MediaPresentationDescription
(
duration
Ms
,
minBufferTimeMs
,
dynamic
,
minUpdateTimeMs
,
periods
);
periods
);
}
}
private
Period
parsePeriod
(
XmlPullParser
xpp
,
String
contentId
,
long
mediaPresentationDuration
)
private
Period
parsePeriod
(
XmlPullParser
xpp
,
String
contentId
,
Uri
baseUrl
,
long
mpdDurationMs
)
throws
XmlPullParserException
,
IOException
{
throws
XmlPullParserException
,
IOException
{
int
id
=
parseInt
(
xpp
,
"id"
);
String
id
=
xpp
.
getAttributeValue
(
null
,
"id"
);
long
start
=
parseDurationMs
(
xpp
,
"start"
,
0
);
long
start
Ms
=
parseDurationMs
(
xpp
,
"start"
,
0
);
long
duration
=
parseDurationMs
(
xpp
,
"duration"
,
mediaPresentationDuration
);
long
duration
Ms
=
parseDurationMs
(
xpp
,
"duration"
,
mpdDurationMs
);
SegmentBase
segmentBase
=
null
;
List
<
AdaptationSet
>
adaptationSets
=
new
ArrayList
<
AdaptationSet
>();
List
<
AdaptationSet
>
adaptationSets
=
new
ArrayList
<
AdaptationSet
>();
List
<
Segment
.
Timeline
>
segmentTimelineList
=
null
;
int
segmentStartNumber
=
0
;
int
segmentTimescale
=
0
;
long
presentationTimeOffset
=
0
;
do
{
do
{
xpp
.
next
();
xpp
.
next
();
if
(
isStartTag
(
xpp
,
"AdaptationSet"
))
{
if
(
isStartTag
(
xpp
,
"BaseURL"
))
{
adaptationSets
.
add
(
parseAdaptationSet
(
xpp
,
contentId
,
start
,
duration
,
baseUrl
=
parseBaseUrl
(
xpp
,
baseUrl
);
segmentTimelineList
));
}
else
if
(
isStartTag
(
xpp
,
"AdaptationSet"
))
{
adaptationSets
.
add
(
parseAdaptationSet
(
xpp
,
contentId
,
baseUrl
,
startMs
,
durationMs
,
segmentBase
));
}
else
if
(
isStartTag
(
xpp
,
"SegmentBase"
))
{
segmentBase
=
parseSegmentBase
(
xpp
,
baseUrl
,
null
);
}
else
if
(
isStartTag
(
xpp
,
"SegmentList"
))
{
}
else
if
(
isStartTag
(
xpp
,
"SegmentList"
))
{
segmentStartNumber
=
parseInt
(
xpp
,
"startNumber"
);
segmentBase
=
parseSegmentList
(
xpp
,
baseUrl
,
null
,
durationMs
);
segmentTimescale
=
parseInt
(
xpp
,
"timescale"
);
}
else
if
(
isStartTag
(
xpp
,
"SegmentTemplate"
))
{
presentationTimeOffset
=
parseLong
(
xpp
,
"presentationTimeOffset"
,
0
);
segmentBase
=
parseSegmentTemplate
(
xpp
,
baseUrl
,
null
,
durationMs
);
segmentTimelineList
=
parsePeriodSegmentList
(
xpp
,
segmentStartNumber
);
}
}
}
while
(!
isEndTag
(
xpp
,
"Period"
));
}
while
(!
isEndTag
(
xpp
,
"Period"
));
return
new
Period
(
id
,
start
,
duration
,
adaptationSets
,
segmentTimelineList
,
return
new
Period
(
id
,
startMs
,
durationMs
,
adaptationSets
);
segmentStartNumber
,
segmentTimescale
,
presentationTimeOffset
);
}
}
private
List
<
Segment
.
Timeline
>
parsePeriodSegmentList
(
// AdaptationSet parsing.
XmlPullParser
xpp
,
long
segmentStartNumber
)
throws
XmlPullParserException
,
IOException
{
List
<
Segment
.
Timeline
>
segmentTimelineList
=
new
ArrayList
<
Segment
.
Timeline
>();
do
{
private
AdaptationSet
parseAdaptationSet
(
XmlPullParser
xpp
,
String
contentId
,
Uri
baseUrl
,
xpp
.
next
();
long
periodStartMs
,
long
periodDurationMs
,
SegmentBase
segmentBase
)
if
(
isStartTag
(
xpp
,
"SegmentTimeline"
))
{
do
{
xpp
.
next
();
if
(
isStartTag
(
xpp
,
"S"
))
{
long
duration
=
parseLong
(
xpp
,
"d"
);
segmentTimelineList
.
add
(
new
Segment
.
Timeline
(
segmentStartNumber
,
duration
));
segmentStartNumber
++;
}
}
while
(!
isEndTag
(
xpp
,
"SegmentTimeline"
));
}
}
while
(!
isEndTag
(
xpp
,
"SegmentList"
));
return
segmentTimelineList
;
}
private
AdaptationSet
parseAdaptationSet
(
XmlPullParser
xpp
,
String
contentId
,
long
periodStart
,
long
periodDuration
,
List
<
Segment
.
Timeline
>
segmentTimelineList
)
throws
XmlPullParserException
,
IOException
{
throws
XmlPullParserException
,
IOException
{
int
id
=
-
1
;
int
contentType
=
AdaptationSet
.
TYPE_UNKNOWN
;
// TODO: Correctly handle other common attributes and elements. See 23009-1 Table 9.
String
mimeType
=
xpp
.
getAttributeValue
(
null
,
"mimeType"
);
String
mimeType
=
xpp
.
getAttributeValue
(
null
,
"mimeType"
);
if
(
mimeType
!=
null
)
{
int
contentType
=
parseAdaptationSetTypeFromMimeType
(
mimeType
);
if
(
MimeTypes
.
isAudio
(
mimeType
))
{
contentType
=
AdaptationSet
.
TYPE_AUDIO
;
}
else
if
(
MimeTypes
.
isVideo
(
mimeType
))
{
contentType
=
AdaptationSet
.
TYPE_VIDEO
;
}
else
if
(
MimeTypes
.
isText
(
mimeType
)
||
mimeType
.
equalsIgnoreCase
(
MimeTypes
.
APPLICATION_TTML
))
{
contentType
=
AdaptationSet
.
TYPE_TEXT
;
}
}
int
id
=
-
1
;
List
<
ContentProtection
>
contentProtections
=
null
;
List
<
ContentProtection
>
contentProtections
=
null
;
List
<
Representation
>
representations
=
new
ArrayList
<
Representation
>();
List
<
Representation
>
representations
=
new
ArrayList
<
Representation
>();
do
{
do
{
xpp
.
next
();
xpp
.
next
();
if
(
contentType
!=
AdaptationSet
.
TYPE_UNKNOWN
)
{
if
(
isStartTag
(
xpp
,
"BaseURL"
))
{
if
(
isStartTag
(
xpp
,
"ContentProtection"
))
{
baseUrl
=
parseBaseUrl
(
xpp
,
baseUrl
);
}
else
if
(
isStartTag
(
xpp
,
"ContentProtection"
))
{
if
(
contentProtections
==
null
)
{
if
(
contentProtections
==
null
)
{
contentProtections
=
new
ArrayList
<
ContentProtection
>();
contentProtections
=
new
ArrayList
<
ContentProtection
>();
}
}
contentProtections
.
add
(
parseContentProtection
(
xpp
));
contentProtections
.
add
(
parseContentProtection
(
xpp
));
}
else
if
(
isStartTag
(
xpp
,
"ContentComponent"
))
{
}
else
if
(
isStartTag
(
xpp
,
"ContentComponent"
))
{
id
=
Integer
.
parseInt
(
xpp
.
getAttributeValue
(
null
,
"id"
));
id
=
Integer
.
parseInt
(
xpp
.
getAttributeValue
(
null
,
"id"
));
String
contentTypeString
=
xpp
.
getAttributeValue
(
null
,
"contentType"
);
contentType
=
checkAdaptationSetTypeConsistency
(
contentType
,
contentType
=
"video"
.
equals
(
contentTypeString
)
?
AdaptationSet
.
TYPE_VIDEO
parseAdaptationSetType
(
xpp
.
getAttributeValue
(
null
,
"contentType"
)));
:
"audio"
.
equals
(
contentTypeString
)
?
AdaptationSet
.
TYPE_AUDIO
:
AdaptationSet
.
TYPE_UNKNOWN
;
}
else
if
(
isStartTag
(
xpp
,
"Representation"
))
{
}
else
if
(
isStartTag
(
xpp
,
"Representation"
))
{
representations
.
add
(
parseRepresentation
(
xpp
,
contentId
,
periodStart
,
periodDuration
,
Representation
representation
=
parseRepresentation
(
xpp
,
contentId
,
baseUrl
,
periodStartMs
,
mimeType
,
segmentTimelineList
));
periodDurationMs
,
mimeType
,
segmentBase
);
}
contentType
=
checkAdaptationSetTypeConsistency
(
contentType
,
parseAdaptationSetTypeFromMimeType
(
representation
.
format
.
mimeType
));
representations
.
add
(
representation
);
}
else
if
(
isStartTag
(
xpp
,
"SegmentBase"
))
{
segmentBase
=
parseSegmentBase
(
xpp
,
baseUrl
,
(
SingleSegmentBase
)
segmentBase
);
}
else
if
(
isStartTag
(
xpp
,
"SegmentList"
))
{
segmentBase
=
parseSegmentList
(
xpp
,
baseUrl
,
(
SegmentList
)
segmentBase
,
periodDurationMs
);
}
else
if
(
isStartTag
(
xpp
,
"SegmentTemplate"
))
{
segmentBase
=
parseSegmentTemplate
(
xpp
,
baseUrl
,
(
SegmentTemplate
)
segmentBase
,
periodDurationMs
);
}
}
}
while
(!
isEndTag
(
xpp
,
"AdaptationSet"
));
}
while
(!
isEndTag
(
xpp
,
"AdaptationSet"
));
return
new
AdaptationSet
(
id
,
contentType
,
representations
,
contentProtections
);
return
new
AdaptationSet
(
id
,
contentType
,
representations
,
contentProtections
);
}
}
private
int
parseAdaptationSetType
(
String
contentType
)
{
return
TextUtils
.
isEmpty
(
contentType
)
?
AdaptationSet
.
TYPE_UNKNOWN
:
MimeTypes
.
BASE_TYPE_AUDIO
.
equals
(
contentType
)
?
AdaptationSet
.
TYPE_AUDIO
:
MimeTypes
.
BASE_TYPE_VIDEO
.
equals
(
contentType
)
?
AdaptationSet
.
TYPE_VIDEO
:
MimeTypes
.
BASE_TYPE_TEXT
.
equals
(
contentType
)
?
AdaptationSet
.
TYPE_TEXT
:
AdaptationSet
.
TYPE_UNKNOWN
;
}
private
int
parseAdaptationSetTypeFromMimeType
(
String
mimeType
)
{
return
TextUtils
.
isEmpty
(
mimeType
)
?
AdaptationSet
.
TYPE_UNKNOWN
:
MimeTypes
.
isAudio
(
mimeType
)
?
AdaptationSet
.
TYPE_AUDIO
:
MimeTypes
.
isVideo
(
mimeType
)
?
AdaptationSet
.
TYPE_VIDEO
:
MimeTypes
.
isText
(
mimeType
)
||
MimeTypes
.
isTtml
(
mimeType
)
?
AdaptationSet
.
TYPE_TEXT
:
AdaptationSet
.
TYPE_UNKNOWN
;
}
/**
* Checks two adaptation set types for consistency, returning the consistent type, or throwing an
* {@link IllegalStateException} if the types are inconsistent.
* <p>
* Two types are consistent if they are equal, or if one is {@link AdaptationSet#TYPE_UNKNOWN}.
* Where one of the types is {@link AdaptationSet#TYPE_UNKNOWN}, the other is returned.
*
* @param firstType The first type.
* @param secondType The second type.
* @return The consistent type.
*/
private
int
checkAdaptationSetTypeConsistency
(
int
firstType
,
int
secondType
)
{
if
(
firstType
==
AdaptationSet
.
TYPE_UNKNOWN
)
{
return
secondType
;
}
else
if
(
secondType
==
AdaptationSet
.
TYPE_UNKNOWN
)
{
return
firstType
;
}
else
{
Assertions
.
checkState
(
firstType
==
secondType
);
return
firstType
;
}
}
/**
/**
* Parses a ContentProtection element.
* Parses a ContentProtection element.
*
*
...
@@ -211,127 +227,208 @@ public class MediaPresentationDescriptionParser extends DefaultHandler {
...
@@ -211,127 +227,208 @@ public class MediaPresentationDescriptionParser extends DefaultHandler {
return
new
ContentProtection
(
schemeUriId
,
null
);
return
new
ContentProtection
(
schemeUriId
,
null
);
}
}
private
Representation
parseRepresentation
(
XmlPullParser
xpp
,
String
contentId
,
long
periodStart
,
// Representation parsing.
long
periodDuration
,
String
parentMimeType
,
List
<
Segment
.
Timeline
>
segmentTimelineList
)
private
Representation
parseRepresentation
(
XmlPullParser
xpp
,
String
contentId
,
Uri
baseUrl
,
long
periodStartMs
,
long
periodDurationMs
,
String
mimeType
,
SegmentBase
segmentBase
)
throws
XmlPullParserException
,
IOException
{
throws
XmlPullParserException
,
IOException
{
int
id
;
String
id
=
xpp
.
getAttributeValue
(
null
,
"id"
);
try
{
int
bandwidth
=
parseInt
(
xpp
,
"bandwidth"
);
id
=
parseInt
(
xpp
,
"id"
);
}
catch
(
NumberFormatException
nfe
)
{
Log
.
d
(
TAG
,
"Unable to parse id; "
+
nfe
.
getMessage
());
// TODO: need a way to generate a unique and stable id; use hashCode for now
id
=
xpp
.
getAttributeValue
(
null
,
"id"
).
hashCode
();
}
int
bandwidth
=
parseInt
(
xpp
,
"bandwidth"
)
/
8
;
int
audioSamplingRate
=
parseInt
(
xpp
,
"audioSamplingRate"
);
int
audioSamplingRate
=
parseInt
(
xpp
,
"audioSamplingRate"
);
int
width
=
parseInt
(
xpp
,
"width"
);
int
width
=
parseInt
(
xpp
,
"width"
);
int
height
=
parseInt
(
xpp
,
"height"
);
int
height
=
parseInt
(
xpp
,
"height"
);
mimeType
=
parseString
(
xpp
,
"mimeType"
,
mimeType
);
String
mimeType
=
xpp
.
getAttributeValue
(
null
,
"mimeType"
);
if
(
mimeType
==
null
)
{
mimeType
=
parentMimeType
;
}
String
representationUrl
=
null
;
long
indexStart
=
-
1
;
long
indexEnd
=
-
1
;
long
initializationStart
=
-
1
;
long
initializationEnd
=
-
1
;
int
numChannels
=
-
1
;
int
numChannels
=
-
1
;
List
<
Segment
>
segmentList
=
null
;
do
{
do
{
xpp
.
next
();
xpp
.
next
();
if
(
isStartTag
(
xpp
,
"BaseURL"
))
{
if
(
isStartTag
(
xpp
,
"BaseURL"
))
{
xpp
.
next
();
baseUrl
=
parseBaseUrl
(
xpp
,
baseUrl
);
representationUrl
=
xpp
.
getText
();
}
else
if
(
isStartTag
(
xpp
,
"AudioChannelConfiguration"
))
{
}
else
if
(
isStartTag
(
xpp
,
"AudioChannelConfiguration"
))
{
numChannels
=
Integer
.
parseInt
(
xpp
.
getAttributeValue
(
null
,
"value"
));
numChannels
=
Integer
.
parseInt
(
xpp
.
getAttributeValue
(
null
,
"value"
));
}
else
if
(
isStartTag
(
xpp
,
"SegmentBase"
))
{
}
else
if
(
isStartTag
(
xpp
,
"SegmentBase"
))
{
String
[]
indexRange
=
xpp
.
getAttributeValue
(
null
,
"indexRange"
).
split
(
"-"
);
segmentBase
=
parseSegmentBase
(
xpp
,
baseUrl
,
(
SingleSegmentBase
)
segmentBase
);
indexStart
=
Long
.
parseLong
(
indexRange
[
0
]);
indexEnd
=
Long
.
parseLong
(
indexRange
[
1
]);
}
else
if
(
isStartTag
(
xpp
,
"SegmentList"
))
{
}
else
if
(
isStartTag
(
xpp
,
"SegmentList"
))
{
segmentList
=
parseRepresentationSegmentList
(
xpp
,
segmentTimelineList
);
segmentBase
=
parseSegmentList
(
xpp
,
baseUrl
,
(
SegmentList
)
segmentBase
,
periodDurationMs
);
}
else
if
(
isStartTag
(
xpp
,
"Initialization"
))
{
}
else
if
(
isStartTag
(
xpp
,
"SegmentTemplate"
))
{
String
[]
indexRange
=
xpp
.
getAttributeValue
(
null
,
"range"
).
split
(
"-"
);
segmentBase
=
parseSegmentTemplate
(
xpp
,
baseUrl
,
(
SegmentTemplate
)
segmentBase
,
initializationStart
=
Long
.
parseLong
(
indexRange
[
0
]);
periodDurationMs
);
initializationEnd
=
Long
.
parseLong
(
indexRange
[
1
]);
}
}
}
while
(!
isEndTag
(
xpp
,
"Representation"
));
}
while
(!
isEndTag
(
xpp
,
"Representation"
));
Uri
uri
=
Uri
.
parse
(
representationUrl
);
Format
format
=
new
Format
(
id
,
mimeType
,
width
,
height
,
numChannels
,
audioSamplingRate
,
Format
format
=
new
Format
(
id
,
mimeType
,
width
,
height
,
numChannels
,
audioSamplingRate
,
bandwidth
);
bandwidth
);
if
(
segmentList
==
null
)
{
return
Representation
.
newInstance
(
periodStartMs
,
periodDurationMs
,
contentId
,
-
1
,
format
,
return
new
Representation
(
contentId
,
-
1
,
format
,
uri
,
DataSpec
.
LENGTH_UNBOUNDED
,
segmentBase
);
initializationStart
,
initializationEnd
,
indexStart
,
indexEnd
,
periodStart
,
periodDuration
);
}
else
{
return
new
SegmentedRepresentation
(
contentId
,
format
,
uri
,
initializationStart
,
initializationEnd
,
indexStart
,
indexEnd
,
periodStart
,
periodDuration
,
segmentList
);
}
}
// SegmentBase, SegmentList and SegmentTemplate parsing.
private
SingleSegmentBase
parseSegmentBase
(
XmlPullParser
xpp
,
Uri
baseUrl
,
SingleSegmentBase
parent
)
throws
XmlPullParserException
,
IOException
{
long
timescale
=
parseLong
(
xpp
,
"timescale"
,
parent
!=
null
?
parent
.
timescale
:
1
);
long
presentationTimeOffset
=
parseLong
(
xpp
,
"presentationTimeOffset"
,
parent
!=
null
?
parent
.
presentationTimeOffset
:
0
);
long
indexStart
=
parent
!=
null
?
parent
.
indexStart
:
0
;
long
indexLength
=
parent
!=
null
?
parent
.
indexLength
:
-
1
;
String
indexRangeText
=
xpp
.
getAttributeValue
(
null
,
"indexRange"
);
if
(
indexRangeText
!=
null
)
{
String
[]
indexRange
=
indexRangeText
.
split
(
"-"
);
indexStart
=
Long
.
parseLong
(
indexRange
[
0
]);
indexLength
=
Long
.
parseLong
(
indexRange
[
1
])
-
indexStart
+
1
;
}
}
private
List
<
Segment
>
parseRepresentationSegmentList
(
XmlPullParser
xpp
,
RangedUri
initialization
=
parent
!=
null
?
parent
.
initialization
:
null
;
List
<
Segment
.
Timeline
>
segmentTimelineList
)
throws
XmlPullParserException
,
IOException
{
do
{
List
<
Segment
>
segmentList
=
new
ArrayList
<
Segment
>();
xpp
.
next
();
int
i
=
0
;
if
(
isStartTag
(
xpp
,
"Initialization"
))
{
initialization
=
parseInitialization
(
xpp
,
baseUrl
);
}
}
while
(!
isEndTag
(
xpp
,
"SegmentBase"
));
return
new
SingleSegmentBase
(
initialization
,
timescale
,
presentationTimeOffset
,
baseUrl
,
indexStart
,
indexLength
);
}
private
SegmentList
parseSegmentList
(
XmlPullParser
xpp
,
Uri
baseUrl
,
SegmentList
parent
,
long
periodDuration
)
throws
XmlPullParserException
,
IOException
{
long
timescale
=
parseLong
(
xpp
,
"timescale"
,
parent
!=
null
?
parent
.
timescale
:
1
);
long
presentationTimeOffset
=
parseLong
(
xpp
,
"presentationTimeOffset"
,
parent
!=
null
?
parent
.
presentationTimeOffset
:
0
);
long
duration
=
parseLong
(
xpp
,
"duration"
,
parent
!=
null
?
parent
.
duration
:
-
1
);
int
startNumber
=
parseInt
(
xpp
,
"startNumber"
,
parent
!=
null
?
parent
.
startNumber
:
0
);
RangedUri
initialization
=
null
;
List
<
SegmentTimelineElement
>
timeline
=
null
;
List
<
RangedUri
>
segments
=
null
;
do
{
do
{
xpp
.
next
();
xpp
.
next
();
if
(
isStartTag
(
xpp
,
"Initialization"
))
{
if
(
isStartTag
(
xpp
,
"Initialization"
))
{
String
url
=
xpp
.
getAttributeValue
(
null
,
"sourceURL"
);
initialization
=
parseInitialization
(
xpp
,
baseUrl
);
String
[]
indexRange
=
xpp
.
getAttributeValue
(
null
,
"range"
).
split
(
"-"
);
}
else
if
(
isStartTag
(
xpp
,
"SegmentTimeline"
))
{
long
initializationStart
=
Long
.
parseLong
(
indexRange
[
0
]);
timeline
=
parseSegmentTimeline
(
xpp
);
long
initializationEnd
=
Long
.
parseLong
(
indexRange
[
1
]);
segmentList
.
add
(
new
Segment
.
Initialization
(
url
,
initializationStart
,
initializationEnd
));
}
else
if
(
isStartTag
(
xpp
,
"SegmentURL"
))
{
}
else
if
(
isStartTag
(
xpp
,
"SegmentURL"
))
{
String
url
=
xpp
.
getAttributeValue
(
null
,
"media"
);
if
(
segments
==
null
)
{
String
mediaRange
=
xpp
.
getAttributeValue
(
null
,
"mediaRange"
);
segments
=
new
ArrayList
<
RangedUri
>();
long
sequenceNumber
=
segmentTimelineList
.
get
(
i
).
sequenceNumber
;
long
duration
=
segmentTimelineList
.
get
(
i
).
duration
;
i
++;
if
(
mediaRange
!=
null
)
{
String
[]
mediaRangeArray
=
xpp
.
getAttributeValue
(
null
,
"mediaRange"
).
split
(
"-"
);
long
mediaStart
=
Long
.
parseLong
(
mediaRangeArray
[
0
]);
segmentList
.
add
(
new
Segment
.
Media
(
url
,
mediaStart
,
sequenceNumber
,
duration
));
}
else
{
segmentList
.
add
(
new
Segment
.
Media
(
url
,
sequenceNumber
,
duration
));
}
}
segments
.
add
(
parseSegmentUrl
(
xpp
,
baseUrl
));
}
}
}
while
(!
isEndTag
(
xpp
,
"SegmentList"
));
}
while
(!
isEndTag
(
xpp
,
"SegmentList"
));
return
segmentList
;
if
(
parent
!=
null
)
{
initialization
=
initialization
!=
null
?
initialization
:
parent
.
initialization
;
timeline
=
timeline
!=
null
?
timeline
:
parent
.
segmentTimeline
;
segments
=
segments
!=
null
?
segments
:
parent
.
mediaSegments
;
}
}
protected
static
boolean
isEndTag
(
XmlPullParser
xpp
,
String
name
)
throws
XmlPullParserException
{
return
new
SegmentList
(
initialization
,
timescale
,
presentationTimeOffset
,
periodDuration
,
return
xpp
.
getEventType
()
==
XmlPullParser
.
END_TAG
&&
name
.
equals
(
xpp
.
getName
()
);
startNumber
,
duration
,
timeline
,
segments
);
}
}
protected
static
boolean
isStartTag
(
XmlPullParser
xpp
,
String
name
)
private
SegmentTemplate
parseSegmentTemplate
(
XmlPullParser
xpp
,
Uri
baseUrl
,
throws
XmlPullParserException
{
SegmentTemplate
parent
,
long
periodDuration
)
throws
XmlPullParserException
,
IOException
{
return
xpp
.
getEventType
()
==
XmlPullParser
.
START_TAG
&&
name
.
equals
(
xpp
.
getName
());
long
timescale
=
parseLong
(
xpp
,
"timescale"
,
parent
!=
null
?
parent
.
timescale
:
1
);
long
presentationTimeOffset
=
parseLong
(
xpp
,
"presentationTimeOffset"
,
parent
!=
null
?
parent
.
presentationTimeOffset
:
0
);
long
duration
=
parseLong
(
xpp
,
"duration"
,
parent
!=
null
?
parent
.
duration
:
-
1
);
int
startNumber
=
parseInt
(
xpp
,
"startNumber"
,
parent
!=
null
?
parent
.
startNumber
:
0
);
UrlTemplate
mediaTemplate
=
parseUrlTemplate
(
xpp
,
"media"
,
parent
!=
null
?
parent
.
mediaTemplate
:
null
);
UrlTemplate
initializationTemplate
=
parseUrlTemplate
(
xpp
,
"initialization"
,
parent
!=
null
?
parent
.
initializationTemplate
:
null
);
RangedUri
initialization
=
null
;
List
<
SegmentTimelineElement
>
timeline
=
null
;
do
{
xpp
.
next
();
if
(
isStartTag
(
xpp
,
"Initialization"
))
{
initialization
=
parseInitialization
(
xpp
,
baseUrl
);
}
else
if
(
isStartTag
(
xpp
,
"SegmentTimeline"
))
{
timeline
=
parseSegmentTimeline
(
xpp
);
}
}
}
while
(!
isEndTag
(
xpp
,
"SegmentTemplate"
));
protected
static
int
parseInt
(
XmlPullParser
xpp
,
String
name
)
{
if
(
parent
!=
null
)
{
String
value
=
xpp
.
getAttributeValue
(
null
,
name
)
;
initialization
=
initialization
!=
null
?
initialization
:
parent
.
initialization
;
return
value
==
null
?
-
1
:
Integer
.
parseInt
(
value
)
;
timeline
=
timeline
!=
null
?
timeline
:
parent
.
segmentTimeline
;
}
}
protected
static
long
parseLong
(
XmlPullParser
xpp
,
String
name
)
{
return
new
SegmentTemplate
(
initialization
,
timescale
,
presentationTimeOffset
,
periodDuration
,
return
parseLong
(
xpp
,
name
,
-
1
);
startNumber
,
duration
,
timeline
,
initializationTemplate
,
mediaTemplate
,
baseUrl
);
}
}
protected
static
long
parseLong
(
XmlPullParser
xpp
,
String
name
,
long
defaultValue
)
{
private
List
<
SegmentTimelineElement
>
parseSegmentTimeline
(
XmlPullParser
xpp
)
String
value
=
xpp
.
getAttributeValue
(
null
,
name
);
throws
XmlPullParserException
,
IOException
{
return
value
==
null
?
defaultValue
:
Long
.
parseLong
(
value
);
List
<
SegmentTimelineElement
>
segmentTimeline
=
new
ArrayList
<
SegmentTimelineElement
>();
long
elapsedTime
=
0
;
do
{
xpp
.
next
();
if
(
isStartTag
(
xpp
,
"S"
))
{
elapsedTime
=
parseLong
(
xpp
,
"t"
,
elapsedTime
);
long
duration
=
parseLong
(
xpp
,
"d"
);
int
count
=
1
+
parseInt
(
xpp
,
"r"
,
0
);
for
(
int
i
=
0
;
i
<
count
;
i
++)
{
segmentTimeline
.
add
(
new
SegmentTimelineElement
(
elapsedTime
,
duration
));
elapsedTime
+=
duration
;
}
}
}
while
(!
isEndTag
(
xpp
,
"SegmentTimeline"
));
return
segmentTimeline
;
}
private
UrlTemplate
parseUrlTemplate
(
XmlPullParser
xpp
,
String
name
,
UrlTemplate
defaultValue
)
{
String
valueString
=
xpp
.
getAttributeValue
(
null
,
name
);
if
(
valueString
!=
null
)
{
return
UrlTemplate
.
compile
(
valueString
);
}
return
defaultValue
;
}
private
RangedUri
parseInitialization
(
XmlPullParser
xpp
,
Uri
baseUrl
)
{
return
parseRangedUrl
(
xpp
,
baseUrl
,
"sourceURL"
,
"range"
);
}
private
RangedUri
parseSegmentUrl
(
XmlPullParser
xpp
,
Uri
baseUrl
)
{
return
parseRangedUrl
(
xpp
,
baseUrl
,
"media"
,
"mediaRange"
);
}
private
RangedUri
parseRangedUrl
(
XmlPullParser
xpp
,
Uri
baseUrl
,
String
urlAttribute
,
String
rangeAttribute
)
{
String
urlText
=
xpp
.
getAttributeValue
(
null
,
urlAttribute
);
long
rangeStart
=
0
;
long
rangeLength
=
-
1
;
String
rangeText
=
xpp
.
getAttributeValue
(
null
,
rangeAttribute
);
if
(
rangeText
!=
null
)
{
String
[]
rangeTextArray
=
rangeText
.
split
(
"-"
);
rangeStart
=
Long
.
parseLong
(
rangeTextArray
[
0
]);
rangeLength
=
Long
.
parseLong
(
rangeTextArray
[
1
])
-
rangeStart
+
1
;
}
}
return
new
RangedUri
(
baseUrl
,
urlText
,
rangeStart
,
rangeLength
);
}
// Utility methods.
private
long
parseDurationMs
(
XmlPullParser
xpp
,
String
name
)
{
protected
static
boolean
isEndTag
(
XmlPullParser
xpp
,
String
name
)
throws
XmlPullParserException
{
return
xpp
.
getEventType
()
==
XmlPullParser
.
END_TAG
&&
name
.
equals
(
xpp
.
getName
());
}
protected
static
boolean
isStartTag
(
XmlPullParser
xpp
,
String
name
)
throws
XmlPullParserException
{
return
xpp
.
getEventType
()
==
XmlPullParser
.
START_TAG
&&
name
.
equals
(
xpp
.
getName
());
}
private
static
long
parseDurationMs
(
XmlPullParser
xpp
,
String
name
)
{
return
parseDurationMs
(
xpp
,
name
,
-
1
);
return
parseDurationMs
(
xpp
,
name
,
-
1
);
}
}
private
long
parseDurationMs
(
XmlPullParser
xpp
,
String
name
,
long
defaultValue
)
{
private
static
long
parseDurationMs
(
XmlPullParser
xpp
,
String
name
,
long
defaultValue
)
{
String
value
=
xpp
.
getAttributeValue
(
null
,
name
);
String
value
=
xpp
.
getAttributeValue
(
null
,
name
);
if
(
value
!=
null
)
{
if
(
value
!=
null
)
{
Matcher
matcher
=
DURATION
.
matcher
(
value
);
Matcher
matcher
=
DURATION
.
matcher
(
value
);
...
@@ -350,4 +447,38 @@ public class MediaPresentationDescriptionParser extends DefaultHandler {
...
@@ -350,4 +447,38 @@ public class MediaPresentationDescriptionParser extends DefaultHandler {
return
defaultValue
;
return
defaultValue
;
}
}
protected
static
Uri
parseBaseUrl
(
XmlPullParser
xpp
,
Uri
parentBaseUrl
)
throws
XmlPullParserException
,
IOException
{
xpp
.
next
();
String
newBaseUrlText
=
xpp
.
getText
();
Uri
newBaseUri
=
Uri
.
parse
(
newBaseUrlText
);
if
(!
newBaseUri
.
isAbsolute
())
{
newBaseUri
=
Uri
.
withAppendedPath
(
parentBaseUrl
,
newBaseUrlText
);
}
return
newBaseUri
;
}
protected
static
int
parseInt
(
XmlPullParser
xpp
,
String
name
)
{
return
parseInt
(
xpp
,
name
,
-
1
);
}
protected
static
int
parseInt
(
XmlPullParser
xpp
,
String
name
,
int
defaultValue
)
{
String
value
=
xpp
.
getAttributeValue
(
null
,
name
);
return
value
==
null
?
defaultValue
:
Integer
.
parseInt
(
value
);
}
protected
static
long
parseLong
(
XmlPullParser
xpp
,
String
name
)
{
return
parseLong
(
xpp
,
name
,
-
1
);
}
protected
static
long
parseLong
(
XmlPullParser
xpp
,
String
name
,
long
defaultValue
)
{
String
value
=
xpp
.
getAttributeValue
(
null
,
name
);
return
value
==
null
?
defaultValue
:
Long
.
parseLong
(
value
);
}
protected
static
String
parseString
(
XmlPullParser
xpp
,
String
name
,
String
defaultValue
)
{
String
value
=
xpp
.
getAttributeValue
(
null
,
name
);
return
value
==
null
?
defaultValue
:
value
;
}
}
}
library/src/main/java/com/google/android/exoplayer/dash/mpd/Period.java
View file @
4228f2cf
...
@@ -23,46 +23,37 @@ import java.util.List;
...
@@ -23,46 +23,37 @@ import java.util.List;
*/
*/
public
final
class
Period
{
public
final
class
Period
{
public
final
int
id
;
/**
* The period identifier, if one exists.
*/
public
final
String
id
;
public
final
long
start
;
/**
* The start time of the period in milliseconds.
*/
public
final
long
startMs
;
public
final
long
duration
;
/**
* The duration of the period in milliseconds, or -1 if the duration is unknown.
*/
public
final
long
durationMs
;
/**
* The adaptation sets belonging to the period.
*/
public
final
List
<
AdaptationSet
>
adaptationSets
;
public
final
List
<
AdaptationSet
>
adaptationSets
;
public
final
List
<
Segment
.
Timeline
>
segmentList
;
/**
* @param id The period identifier. May be null.
public
final
int
segmentStartNumber
;
* @param start The start time of the period in milliseconds.
* @param duration The duration of the period in milliseconds, or -1 if the duration is unknown.
public
final
int
segmentTimescale
;
* @param adaptationSets The adaptation sets belonging to the period.
*/
public
final
long
presentationTimeOffset
;
public
Period
(
String
id
,
long
start
,
long
duration
,
List
<
AdaptationSet
>
adaptationSets
)
{
public
Period
(
int
id
,
long
start
,
long
duration
,
List
<
AdaptationSet
>
adaptationSets
)
{
this
(
id
,
start
,
duration
,
adaptationSets
,
null
,
0
,
0
,
0
);
}
public
Period
(
int
id
,
long
start
,
long
duration
,
List
<
AdaptationSet
>
adaptationSets
,
List
<
Segment
.
Timeline
>
segmentList
,
int
segmentStartNumber
,
int
segmentTimescale
)
{
this
(
id
,
start
,
duration
,
adaptationSets
,
segmentList
,
segmentStartNumber
,
segmentTimescale
,
0
);
}
public
Period
(
int
id
,
long
start
,
long
duration
,
List
<
AdaptationSet
>
adaptationSets
,
List
<
Segment
.
Timeline
>
segmentList
,
int
segmentStartNumber
,
int
segmentTimescale
,
long
presentationTimeOffset
)
{
this
.
id
=
id
;
this
.
id
=
id
;
this
.
start
=
start
;
this
.
start
Ms
=
start
;
this
.
duration
=
duration
;
this
.
duration
Ms
=
duration
;
this
.
adaptationSets
=
Collections
.
unmodifiableList
(
adaptationSets
);
this
.
adaptationSets
=
Collections
.
unmodifiableList
(
adaptationSets
);
if
(
segmentList
!=
null
)
{
this
.
segmentList
=
Collections
.
unmodifiableList
(
segmentList
);
}
else
{
this
.
segmentList
=
null
;
}
this
.
segmentStartNumber
=
segmentStartNumber
;
this
.
segmentTimescale
=
segmentTimescale
;
this
.
presentationTimeOffset
=
presentationTimeOffset
;
}
}
}
}
library/src/main/java/com/google/android/exoplayer/dash/mpd/RangedUri.java
0 → 100644
View file @
4228f2cf
/*
* 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
.
dash
.
mpd
;
import
com.google.android.exoplayer.util.Assertions
;
import
android.net.Uri
;
/**
* Defines a range of data located at a {@link Uri}.
*/
public
final
class
RangedUri
{
/**
* The (zero based) index of the first byte of the range.
*/
public
final
long
start
;
/**
* The length of the range, or -1 to indicate that the range is unbounded.
*/
public
final
long
length
;
// The {@link Uri} is stored internally in two parts, {@link #baseUri} and {@link uriString}.
// This helps optimize memory usage in the same way that DASH manifests allow many URLs to be
// expressed concisely in the form of a single BaseURL and many relative paths. Note that this
// optimization relies on the same {@code Uri} being passed as the {@link #baseUri} to many
// instances of this class.
private
final
Uri
baseUri
;
private
final
String
stringUri
;
private
int
hashCode
;
/**
* Constructs an ranged uri.
* <p>
* The uri is built according to the following rules:
* <ul>
* <li>If {@code baseUri} is null or if {@code stringUri} is absolute, then {@code baseUri} is
* ignored and the url consists solely of {@code stringUri}.
* <li>If {@code stringUri} is null, then the url consists solely of {@code baseUrl}.
* <li>Otherwise, the url consists of the concatenation of {@code baseUri} and {@code stringUri}.
* </ul>
*
* @param baseUri An uri that can form the base of the uri defined by the instance.
* @param stringUri A relative or absolute uri in string form.
* @param start The (zero based) index of the first byte of the range.
* @param length The length of the range, or -1 to indicate that the range is unbounded.
*/
public
RangedUri
(
Uri
baseUri
,
String
stringUri
,
long
start
,
long
length
)
{
Assertions
.
checkArgument
(
baseUri
!=
null
||
stringUri
!=
null
);
this
.
baseUri
=
baseUri
;
this
.
stringUri
=
stringUri
;
this
.
start
=
start
;
this
.
length
=
length
;
}
/**
* Returns the {@link Uri} represented by the instance.
*
* @return The {@link Uri} represented by the instance.
*/
public
Uri
getUri
()
{
if
(
stringUri
==
null
)
{
return
baseUri
;
}
Uri
uri
=
Uri
.
parse
(
stringUri
);
if
(!
uri
.
isAbsolute
()
&&
baseUri
!=
null
)
{
uri
=
Uri
.
withAppendedPath
(
baseUri
,
stringUri
);
}
return
uri
;
}
/**
* Attempts to merge this {@link RangedUri} with another.
* <p>
* A merge is successful if both instances define the same {@link Uri}, and if one starte the
* byte after the other ends, forming a contiguous region with no overlap.
* <p>
* If {@code other} is null then the merge is considered unsuccessful, and null is returned.
*
* @param other The {@link RangedUri} to merge.
* @return The merged {@link RangedUri} if the merge was successful. Null otherwise.
*/
public
RangedUri
attemptMerge
(
RangedUri
other
)
{
if
(
other
==
null
||
!
getUri
().
equals
(
other
.
getUri
()))
{
return
null
;
}
else
if
(
length
!=
-
1
&&
start
+
length
==
other
.
start
)
{
return
new
RangedUri
(
baseUri
,
stringUri
,
start
,
other
.
length
==
-
1
?
-
1
:
length
+
other
.
length
);
}
else
if
(
other
.
length
!=
-
1
&&
other
.
start
+
other
.
length
==
start
)
{
return
new
RangedUri
(
baseUri
,
stringUri
,
other
.
start
,
length
==
-
1
?
-
1
:
other
.
length
+
length
);
}
else
{
return
null
;
}
}
@Override
public
int
hashCode
()
{
if
(
hashCode
==
0
)
{
int
result
=
17
;
result
=
31
*
result
+
(
int
)
start
;
result
=
31
*
result
+
(
int
)
length
;
result
=
31
*
result
+
getUri
().
hashCode
();
hashCode
=
result
;
}
return
hashCode
;
}
@Override
public
boolean
equals
(
Object
obj
)
{
if
(
this
==
obj
)
{
return
true
;
}
if
(
obj
==
null
||
getClass
()
!=
obj
.
getClass
())
{
return
false
;
}
RangedUri
other
=
(
RangedUri
)
obj
;
return
this
.
start
==
other
.
start
&&
this
.
length
==
other
.
length
&&
getUri
().
equals
(
other
.
getUri
());
}
}
library/src/main/java/com/google/android/exoplayer/dash/mpd/Representation.java
View file @
4228f2cf
...
@@ -16,13 +16,16 @@
...
@@ -16,13 +16,16 @@
package
com
.
google
.
android
.
exoplayer
.
dash
.
mpd
;
package
com
.
google
.
android
.
exoplayer
.
dash
.
mpd
;
import
com.google.android.exoplayer.chunk.Format
;
import
com.google.android.exoplayer.chunk.Format
;
import
com.google.android.exoplayer.dash.DashSegmentIndex
;
import
com.google.android.exoplayer.dash.mpd.SegmentBase.MultiSegmentBase
;
import
com.google.android.exoplayer.dash.mpd.SegmentBase.SingleSegmentBase
;
import
android.net.Uri
;
import
android.net.Uri
;
/**
/**
* A
flat version of a
DASH representation.
* A DASH representation.
*/
*/
public
class
Representation
{
public
abstract
class
Representation
{
/**
/**
* Identifies the piece of content to which this {@link Representation} belongs.
* Identifies the piece of content to which this {@link Representation} belongs.
...
@@ -33,7 +36,7 @@ public class Representation {
...
@@ -33,7 +36,7 @@ public class Representation {
public
final
String
contentId
;
public
final
String
contentId
;
/**
/**
* Identifies the revision of the
{@link Representation}
.
* Identifies the revision of the
content
.
* <p>
* <p>
* If the media for a given ({@link #contentId} can change over time without a change to the
* If the media for a given ({@link #contentId} can change over time without a change to the
* {@link #format}'s {@link Format#id} (e.g. as a result of re-encoding the media with an
* {@link #format}'s {@link Format#id} (e.g. as a result of re-encoding the media with an
...
@@ -43,45 +46,93 @@ public class Representation {
...
@@ -43,45 +46,93 @@ public class Representation {
public
final
long
revisionId
;
public
final
long
revisionId
;
/**
/**
* The format
in which the {@link Representation} is encoded
.
* The format
of the representation
.
*/
*/
public
final
Format
format
;
public
final
Format
format
;
public
final
long
contentLength
;
/**
* The start time of the enclosing period in milliseconds since the epoch.
public
final
long
initializationStart
;
*/
public
final
long
periodStartMs
;
public
final
long
initializationEnd
;
public
final
long
indexStart
;
public
final
long
indexEnd
;
/**
* The duration of the enclosing period in milliseconds.
*/
public
final
long
periodDurationMs
;
public
final
long
periodStart
;
/**
* The offset of the presentation timestamps in the media stream relative to media time.
*/
public
final
long
presentationTimeOffsetMs
;
p
ublic
final
long
periodDuration
;
p
rivate
final
RangedUri
initializationUri
;
public
final
Uri
uri
;
/**
* Constructs a new instance.
*
* @param periodStartMs The start time of the enclosing period in milliseconds.
* @param periodDurationMs The duration of the enclosing period in milliseconds, or -1 if the
* duration is unknown.
* @param contentId Identifies the piece of content to which this representation belongs.
* @param revisionId Identifies the revision of the content.
* @param format The format of the representation.
* @param segmentBase A segment base element for the representation.
* @return The constructed instance.
*/
public
static
Representation
newInstance
(
long
periodStartMs
,
long
periodDurationMs
,
String
contentId
,
long
revisionId
,
Format
format
,
SegmentBase
segmentBase
)
{
if
(
segmentBase
instanceof
SingleSegmentBase
)
{
return
new
SingleSegmentRepresentation
(
periodStartMs
,
periodDurationMs
,
contentId
,
revisionId
,
format
,
(
SingleSegmentBase
)
segmentBase
,
-
1
);
}
else
if
(
segmentBase
instanceof
MultiSegmentBase
)
{
return
new
MultiSegmentRepresentation
(
periodStartMs
,
periodDurationMs
,
contentId
,
revisionId
,
format
,
(
MultiSegmentBase
)
segmentBase
);
}
else
{
throw
new
IllegalArgumentException
(
"segmentBase must be of type SingleSegmentBase or "
+
"MultiSegmentBase"
);
}
}
public
Representation
(
String
contentId
,
long
revisionId
,
Format
format
,
Uri
uri
,
private
Representation
(
long
periodStartMs
,
long
periodDurationMs
,
String
contentId
,
long
contentLength
,
long
initializationStart
,
long
initializationEnd
,
long
indexStart
,
long
revisionId
,
Format
format
,
SegmentBase
segmentBase
)
{
long
indexEnd
,
long
periodStart
,
long
periodDuration
)
{
this
.
periodStartMs
=
periodStartMs
;
this
.
periodDurationMs
=
periodDurationMs
;
this
.
contentId
=
contentId
;
this
.
contentId
=
contentId
;
this
.
revisionId
=
revisionId
;
this
.
revisionId
=
revisionId
;
this
.
format
=
format
;
this
.
format
=
format
;
this
.
contentLength
=
contentLength
;
initializationUri
=
segmentBase
.
getInitialization
(
this
);
this
.
initializationStart
=
initializationStart
;
presentationTimeOffsetMs
=
(
segmentBase
.
presentationTimeOffset
*
1000
)
/
segmentBase
.
timescale
;
this
.
initializationEnd
=
initializationEnd
;
}
this
.
indexStart
=
indexStart
;
this
.
indexEnd
=
indexEnd
;
/**
this
.
periodStart
=
periodStart
;
* Gets a {@link RangedUri} defining the location of the representation's initialization data.
this
.
periodDuration
=
periodDuration
;
* May be null if no initialization data exists.
this
.
uri
=
uri
;
*
* @return A {@link RangedUri} defining the location of the initialization data, or null.
*/
public
RangedUri
getInitializationUri
()
{
return
initializationUri
;
}
}
/**
/**
* Gets a {@link RangedUri} defining the location of the representation's segment index. Null if
* the representation provides an index directly.
*
* @return The location of the segment index, or null.
*/
public
abstract
RangedUri
getIndexUri
();
/**
* Gets a segment index, if the representation is able to provide one directly. Null if the
* segment index is defined externally.
*
* @return The segment index, or null.
*/
public
abstract
DashSegmentIndex
getIndex
();
/**
* Generates a cache key for the {@link Representation}, in the format
* Generates a cache key for the {@link Representation}, in the format
* {@
link #contentId}.{@link #format.id}.{@link #
revisionId}.
* {@
code contentId + "." + format.id + "." +
revisionId}.
*
*
* @return A cache key.
* @return A cache key.
*/
*/
...
@@ -89,4 +140,143 @@ public class Representation {
...
@@ -89,4 +140,143 @@ public class Representation {
return
contentId
+
"."
+
format
.
id
+
"."
+
revisionId
;
return
contentId
+
"."
+
format
.
id
+
"."
+
revisionId
;
}
}
/**
* A DASH representation consisting of a single segment.
*/
public
static
class
SingleSegmentRepresentation
extends
Representation
{
/**
* The {@link Uri} of the single segment.
*/
public
final
Uri
uri
;
/**
* The content length, or -1 if unknown.
*/
public
final
long
contentLength
;
private
final
RangedUri
indexUri
;
/**
* @param periodStartMs The start time of the enclosing period in milliseconds.
* @param periodDurationMs The duration of the enclosing period in milliseconds, or -1 if the
* duration is unknown.
* @param contentId Identifies the piece of content to which this representation belongs.
* @param revisionId Identifies the revision of the content.
* @param format The format of the representation.
* @param uri The uri of the media.
* @param initializationStart The offset of the first byte of initialization data.
* @param initializationEnd The offset of the last byte of initialization data.
* @param indexStart The offset of the first byte of index data.
* @param indexEnd The offset of the last byte of index data.
* @param contentLength The content length, or -1 if unknown.
*/
public
static
SingleSegmentRepresentation
newInstance
(
long
periodStartMs
,
long
periodDurationMs
,
String
contentId
,
long
revisionId
,
Format
format
,
Uri
uri
,
long
initializationStart
,
long
initializationEnd
,
long
indexStart
,
long
indexEnd
,
long
contentLength
)
{
RangedUri
rangedUri
=
new
RangedUri
(
uri
,
null
,
initializationStart
,
initializationEnd
-
initializationStart
+
1
);
SingleSegmentBase
segmentBase
=
new
SingleSegmentBase
(
rangedUri
,
1
,
0
,
uri
,
indexStart
,
indexEnd
-
indexStart
+
1
);
return
new
SingleSegmentRepresentation
(
periodStartMs
,
periodDurationMs
,
contentId
,
revisionId
,
format
,
segmentBase
,
contentLength
);
}
/**
* @param periodStartMs The start time of the enclosing period in milliseconds.
* @param periodDurationMs The duration of the enclosing period in milliseconds, or -1 if the
* duration is unknown.
* @param contentId Identifies the piece of content to which this representation belongs.
* @param revisionId Identifies the revision of the content.
* @param format The format of the representation.
* @param segmentBase The segment base underlying the representation.
* @param contentLength The content length, or -1 if unknown.
*/
public
SingleSegmentRepresentation
(
long
periodStartMs
,
long
periodDurationMs
,
String
contentId
,
long
revisionId
,
Format
format
,
SingleSegmentBase
segmentBase
,
long
contentLength
)
{
super
(
periodStartMs
,
periodDurationMs
,
contentId
,
revisionId
,
format
,
segmentBase
);
this
.
uri
=
segmentBase
.
uri
;
this
.
indexUri
=
segmentBase
.
getIndex
();
this
.
contentLength
=
contentLength
;
}
@Override
public
RangedUri
getIndexUri
()
{
return
indexUri
;
}
@Override
public
DashSegmentIndex
getIndex
()
{
return
null
;
}
}
/**
* A DASH representation consisting of multiple segments.
*/
public
static
class
MultiSegmentRepresentation
extends
Representation
implements
DashSegmentIndex
{
private
final
MultiSegmentBase
segmentBase
;
/**
* @param periodStartMs The start time of the enclosing period in milliseconds.
* @param periodDurationMs The duration of the enclosing period in milliseconds, or -1 if the
* duration is unknown.
* @param contentId Identifies the piece of content to which this representation belongs.
* @param revisionId Identifies the revision of the content.
* @param format The format of the representation.
* @param segmentBase The segment base underlying the representation.
*/
public
MultiSegmentRepresentation
(
long
periodStartMs
,
long
periodDurationMs
,
String
contentId
,
long
revisionId
,
Format
format
,
MultiSegmentBase
segmentBase
)
{
super
(
periodStartMs
,
periodDurationMs
,
contentId
,
revisionId
,
format
,
segmentBase
);
this
.
segmentBase
=
segmentBase
;
}
@Override
public
RangedUri
getIndexUri
()
{
return
null
;
}
@Override
public
DashSegmentIndex
getIndex
()
{
return
this
;
}
// DashSegmentIndex implementation.
@Override
public
RangedUri
getSegmentUrl
(
int
segmentIndex
)
{
return
segmentBase
.
getSegmentUrl
(
this
,
segmentIndex
);
}
@Override
public
int
getSegmentNum
(
long
timeUs
)
{
return
segmentBase
.
getSegmentNum
(
timeUs
);
}
@Override
public
long
getTimeUs
(
int
segmentIndex
)
{
return
segmentBase
.
getSegmentTimeUs
(
segmentIndex
);
}
@Override
public
long
getDurationUs
(
int
segmentIndex
)
{
return
segmentBase
.
getSegmentDurationUs
(
segmentIndex
);
}
@Override
public
int
getFirstSegmentNum
()
{
return
segmentBase
.
getFirstSegmentNum
();
}
@Override
public
int
getLastSegmentNum
()
{
return
segmentBase
.
getLastSegmentNum
();
}
}
}
}
library/src/main/java/com/google/android/exoplayer/dash/mpd/Segment.java
deleted
100644 → 0
View file @
273fad84
/*
* 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
.
dash
.
mpd
;
/**
* Represents a particular segment in a Representation.
*
*/
public
abstract
class
Segment
{
public
final
String
relativeUri
;
public
final
long
sequenceNumber
;
public
final
long
duration
;
public
Segment
(
String
relativeUri
,
long
sequenceNumber
,
long
duration
)
{
this
.
relativeUri
=
relativeUri
;
this
.
sequenceNumber
=
sequenceNumber
;
this
.
duration
=
duration
;
}
/**
* Represents a timeline segment from the MPD's SegmentTimeline list.
*/
public
static
class
Timeline
extends
Segment
{
public
Timeline
(
long
sequenceNumber
,
long
duration
)
{
super
(
null
,
sequenceNumber
,
duration
);
}
}
/**
* Represents an initialization segment.
*/
public
static
class
Initialization
extends
Segment
{
public
final
long
initializationStart
;
public
final
long
initializationEnd
;
public
Initialization
(
String
relativeUri
,
long
initializationStart
,
long
initializationEnd
)
{
super
(
relativeUri
,
-
1
,
-
1
);
this
.
initializationStart
=
initializationStart
;
this
.
initializationEnd
=
initializationEnd
;
}
}
/**
* Represents a media segment.
*/
public
static
class
Media
extends
Segment
{
public
final
long
mediaStart
;
public
Media
(
String
relativeUri
,
long
sequenceNumber
,
long
duration
)
{
this
(
relativeUri
,
0
,
sequenceNumber
,
duration
);
}
public
Media
(
String
uri
,
long
mediaStart
,
long
sequenceNumber
,
long
duration
)
{
super
(
uri
,
sequenceNumber
,
duration
);
this
.
mediaStart
=
mediaStart
;
}
}
}
library/src/main/java/com/google/android/exoplayer/dash/mpd/SegmentedRepresentation.java
deleted
100644 → 0
View file @
273fad84
/*
* 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
.
dash
.
mpd
;
import
com.google.android.exoplayer.chunk.Format
;
import
com.google.android.exoplayer.upstream.DataSpec
;
import
android.net.Uri
;
import
java.util.List
;
/**
* Represents a DASH Representation which uses the SegmentList structure (i.e. it has a list of
* Segment URLs instead of a single URL).
*/
public
class
SegmentedRepresentation
extends
Representation
{
private
List
<
Segment
>
segmentList
;
public
SegmentedRepresentation
(
String
contentId
,
Format
format
,
Uri
uri
,
long
initializationStart
,
long
initializationEnd
,
long
indexStart
,
long
indexEnd
,
long
periodStart
,
long
periodDuration
,
List
<
Segment
>
segmentList
)
{
super
(
contentId
,
-
1
,
format
,
uri
,
DataSpec
.
LENGTH_UNBOUNDED
,
initializationStart
,
initializationEnd
,
indexStart
,
indexEnd
,
periodStart
,
periodDuration
);
this
.
segmentList
=
segmentList
;
}
public
int
getNumSegments
()
{
return
segmentList
.
size
();
}
public
Segment
getSegment
(
int
i
)
{
return
segmentList
.
get
(
i
);
}
}
library/src/main/java/com/google/android/exoplayer/dash/mpd/UrlTemplate.java
0 → 100644
View file @
4228f2cf
/*
* 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
.
dash
.
mpd
;
/**
* A template from which URLs can be built.
* <p>
* URLs are built according to the substitution rules defined in ISO/IEC 23009-1:2014 5.3.9.4.4.
*/
public
final
class
UrlTemplate
{
private
static
final
String
REPRESENTATION
=
"RepresentationID"
;
private
static
final
String
NUMBER
=
"Number"
;
private
static
final
String
BANDWIDTH
=
"Bandwidth"
;
private
static
final
String
TIME
=
"Time"
;
private
static
final
String
ESCAPED_DOLLAR
=
"$$"
;
private
static
final
String
DEFAULT_FORMAT_TAG
=
"%01d"
;
private
static
final
int
REPRESENTATION_ID
=
1
;
private
static
final
int
NUMBER_ID
=
2
;
private
static
final
int
BANDWIDTH_ID
=
3
;
private
static
final
int
TIME_ID
=
4
;
private
final
String
[]
urlPieces
;
private
final
int
[]
identifiers
;
private
final
String
[]
identifierFormatTags
;
private
final
int
identifierCount
;
/**
* Compile an instance from the provided template string.
*
* @param template The template.
* @return The compiled instance.
* @throws IllegalArgumentException If the template string is malformed.
*/
public
static
UrlTemplate
compile
(
String
template
)
{
// These arrays are sizes assuming each of the four possible identifiers will be present at
// most once in the template, which seems like a reasonable assumption.
String
[]
urlPieces
=
new
String
[
5
];
int
[]
identifiers
=
new
int
[
4
];
String
[]
identifierFormatTags
=
new
String
[
4
];
int
identifierCount
=
parseTemplate
(
template
,
urlPieces
,
identifiers
,
identifierFormatTags
);
return
new
UrlTemplate
(
urlPieces
,
identifiers
,
identifierFormatTags
,
identifierCount
);
}
/**
* Internal constructor. Use {@link #compile(String)} to build instances of this class.
*/
private
UrlTemplate
(
String
[]
urlPieces
,
int
[]
identifiers
,
String
[]
identifierFormatTags
,
int
identifierCount
)
{
this
.
urlPieces
=
urlPieces
;
this
.
identifiers
=
identifiers
;
this
.
identifierFormatTags
=
identifierFormatTags
;
this
.
identifierCount
=
identifierCount
;
}
/**
* Constructs a Uri from the template, substituting in the provided arguments.
* <p>
* Arguments whose corresponding identifiers are not present in the template will be ignored.
*
* @param representationId The representation identifier.
* @param segmentNumber The segment number.
* @param bandwidth The bandwidth.
* @param time The time as specified by the segment timeline.
* @return The built Uri.
*/
public
String
buildUri
(
String
representationId
,
int
segmentNumber
,
int
bandwidth
,
long
time
)
{
StringBuilder
builder
=
new
StringBuilder
();
for
(
int
i
=
0
;
i
<
identifierCount
;
i
++)
{
builder
.
append
(
urlPieces
[
i
]);
if
(
identifiers
[
i
]
==
REPRESENTATION_ID
)
{
builder
.
append
(
representationId
);
}
else
if
(
identifiers
[
i
]
==
NUMBER_ID
)
{
builder
.
append
(
String
.
format
(
identifierFormatTags
[
i
],
segmentNumber
));
}
else
if
(
identifiers
[
i
]
==
BANDWIDTH_ID
)
{
builder
.
append
(
String
.
format
(
identifierFormatTags
[
i
],
bandwidth
));
}
else
if
(
identifiers
[
i
]
==
TIME_ID
)
{
builder
.
append
(
String
.
format
(
identifierFormatTags
[
i
],
time
));
}
}
builder
.
append
(
urlPieces
[
identifierCount
]);
return
builder
.
toString
();
}
/**
* Parses {@code template}, placing the decomposed components into the provided arrays.
* <p>
* If the return value is N, {@code urlPieces} will contain (N+1) strings that must be
* interleaved with N arguments in order to construct a url. The N identifiers that correspond to
* the required arguments, together with the tags that define their required formatting, are
* returned in {@code identifiers} and {@code identifierFormatTags} respectively.
*
* @param template The template to parse.
* @param urlPieces A holder for pieces of url parsed from the template.
* @param identifiers A holder for identifiers parsed from the template.
* @param identifierFormatTags A holder for format tags corresponding to the parsed identifiers.
* @return The number of identifiers in the template url.
* @throws IllegalArgumentException If the template string is malformed.
*/
private
static
int
parseTemplate
(
String
template
,
String
[]
urlPieces
,
int
[]
identifiers
,
String
[]
identifierFormatTags
)
{
urlPieces
[
0
]
=
""
;
int
templateIndex
=
0
;
int
identifierCount
=
0
;
while
(
templateIndex
<
template
.
length
())
{
int
dollarIndex
=
template
.
indexOf
(
"$"
,
templateIndex
);
if
(
dollarIndex
==
-
1
)
{
urlPieces
[
identifierCount
]
+=
template
.
substring
(
templateIndex
);
templateIndex
=
template
.
length
();
}
else
if
(
dollarIndex
!=
templateIndex
)
{
urlPieces
[
identifierCount
]
+=
template
.
substring
(
templateIndex
,
dollarIndex
);
templateIndex
=
dollarIndex
;
}
else
if
(
template
.
startsWith
(
ESCAPED_DOLLAR
,
templateIndex
))
{
urlPieces
[
identifierCount
]
+=
"$"
;
templateIndex
+=
2
;
}
else
{
int
secondIndex
=
template
.
indexOf
(
"$"
,
templateIndex
+
1
);
String
identifier
=
template
.
substring
(
templateIndex
+
1
,
secondIndex
);
if
(
identifier
.
equals
(
REPRESENTATION
))
{
identifiers
[
identifierCount
]
=
REPRESENTATION_ID
;
}
else
{
int
formatTagIndex
=
identifier
.
indexOf
(
"%0"
);
String
formatTag
=
DEFAULT_FORMAT_TAG
;
if
(
formatTagIndex
!=
-
1
)
{
formatTag
=
identifier
.
substring
(
formatTagIndex
);
if
(!
formatTag
.
endsWith
(
"d"
))
{
formatTag
+=
"d"
;
}
identifier
=
identifier
.
substring
(
0
,
formatTagIndex
);
}
if
(
identifier
.
equals
(
NUMBER
))
{
identifiers
[
identifierCount
]
=
NUMBER_ID
;
}
else
if
(
identifier
.
equals
(
BANDWIDTH
))
{
identifiers
[
identifierCount
]
=
BANDWIDTH_ID
;
}
else
if
(
identifier
.
equals
(
TIME
))
{
identifiers
[
identifierCount
]
=
TIME_ID
;
}
else
{
throw
new
IllegalArgumentException
(
"Invalid template: "
+
template
);
}
identifierFormatTags
[
identifierCount
]
=
formatTag
;
}
identifierCount
++;
urlPieces
[
identifierCount
]
=
""
;
templateIndex
=
secondIndex
+
1
;
}
}
return
identifierCount
;
}
}
library/src/main/java/com/google/android/exoplayer/parser/mp4/Atom.java
View file @
4228f2cf
...
@@ -21,6 +21,7 @@ import java.util.List;
...
@@ -21,6 +21,7 @@ import java.util.List;
/* package */
abstract
class
Atom
{
/* package */
abstract
class
Atom
{
public
static
final
int
TYPE_avc1
=
0x61766331
;
public
static
final
int
TYPE_avc1
=
0x61766331
;
public
static
final
int
TYPE_avc3
=
0x61766333
;
public
static
final
int
TYPE_esds
=
0x65736473
;
public
static
final
int
TYPE_esds
=
0x65736473
;
public
static
final
int
TYPE_mdat
=
0x6D646174
;
public
static
final
int
TYPE_mdat
=
0x6D646174
;
public
static
final
int
TYPE_mfhd
=
0x6D666864
;
public
static
final
int
TYPE_mfhd
=
0x6D666864
;
...
...
library/src/main/java/com/google/android/exoplayer/parser/mp4/FragmentedMp4Extractor.java
View file @
4228f2cf
...
@@ -50,6 +50,15 @@ import java.util.UUID;
...
@@ -50,6 +50,15 @@ import java.util.UUID;
public
final
class
FragmentedMp4Extractor
{
public
final
class
FragmentedMp4Extractor
{
/**
/**
* Flag to work around an issue in some video streams where every frame is marked as a sync frame.
* The workaround overrides the sync frame flags in the stream, forcing them to false except for
* the first sample in each segment.
* <p>
* This flag does nothing if the stream is not a video stream.
*/
public
static
final
int
WORKAROUND_EVERY_VIDEO_FRAME_IS_SYNC_FRAME
=
1
;
/**
* An attempt to read from the input stream returned 0 bytes of data.
* An attempt to read from the input stream returned 0 bytes of data.
*/
*/
public
static
final
int
RESULT_NEED_MORE_DATA
=
1
;
public
static
final
int
RESULT_NEED_MORE_DATA
=
1
;
...
@@ -74,9 +83,13 @@ public final class FragmentedMp4Extractor {
...
@@ -74,9 +83,13 @@ public final class FragmentedMp4Extractor {
* A sidx atom was read. The parsed data can be read using {@link #getSegmentIndex()}.
* A sidx atom was read. The parsed data can be read using {@link #getSegmentIndex()}.
*/
*/
public
static
final
int
RESULT_READ_SIDX
=
32
;
public
static
final
int
RESULT_READ_SIDX
=
32
;
/**
* The next thing to be read is a sample, but a {@link SampleHolder} was not supplied.
*/
public
static
final
int
RESULT_NEED_SAMPLE_HOLDER
=
64
;
private
static
final
int
READ_TERMINATING_RESULTS
=
RESULT_NEED_MORE_DATA
|
RESULT_END_OF_STREAM
private
static
final
int
READ_TERMINATING_RESULTS
=
RESULT_NEED_MORE_DATA
|
RESULT_END_OF_STREAM
|
RESULT_READ_SAMPLE_FULL
;
|
RESULT_READ_SAMPLE_FULL
|
RESULT_NEED_SAMPLE_HOLDER
;
private
static
final
byte
[]
NAL_START_CODE
=
new
byte
[]
{
0
,
0
,
0
,
1
};
private
static
final
byte
[]
NAL_START_CODE
=
new
byte
[]
{
0
,
0
,
0
,
1
};
private
static
final
byte
[]
PIFF_SAMPLE_ENCRYPTION_BOX_EXTENDED_TYPE
=
private
static
final
byte
[]
PIFF_SAMPLE_ENCRYPTION_BOX_EXTENDED_TYPE
=
new
byte
[]
{-
94
,
57
,
79
,
82
,
90
,
-
101
,
79
,
20
,
-
94
,
68
,
108
,
66
,
124
,
100
,
-
115
,
-
12
};
new
byte
[]
{-
94
,
57
,
79
,
82
,
90
,
-
101
,
79
,
20
,
-
94
,
68
,
108
,
66
,
124
,
100
,
-
115
,
-
12
};
...
@@ -97,6 +110,7 @@ public final class FragmentedMp4Extractor {
...
@@ -97,6 +110,7 @@ public final class FragmentedMp4Extractor {
static
{
static
{
HashSet
<
Integer
>
parsedAtoms
=
new
HashSet
<
Integer
>();
HashSet
<
Integer
>
parsedAtoms
=
new
HashSet
<
Integer
>();
parsedAtoms
.
add
(
Atom
.
TYPE_avc1
);
parsedAtoms
.
add
(
Atom
.
TYPE_avc1
);
parsedAtoms
.
add
(
Atom
.
TYPE_avc3
);
parsedAtoms
.
add
(
Atom
.
TYPE_esds
);
parsedAtoms
.
add
(
Atom
.
TYPE_esds
);
parsedAtoms
.
add
(
Atom
.
TYPE_hdlr
);
parsedAtoms
.
add
(
Atom
.
TYPE_hdlr
);
parsedAtoms
.
add
(
Atom
.
TYPE_mdat
);
parsedAtoms
.
add
(
Atom
.
TYPE_mdat
);
...
@@ -140,7 +154,7 @@ public final class FragmentedMp4Extractor {
...
@@ -140,7 +154,7 @@ public final class FragmentedMp4Extractor {
CONTAINER_TYPES
=
Collections
.
unmodifiableSet
(
atomContainerTypes
);
CONTAINER_TYPES
=
Collections
.
unmodifiableSet
(
atomContainerTypes
);
}
}
private
final
boolean
enableSmoothStreamingWorkaround
s
;
private
final
int
workaroundFlag
s
;
// Parser state
// Parser state
private
final
ParsableByteArray
atomHeader
;
private
final
ParsableByteArray
atomHeader
;
...
@@ -172,16 +186,15 @@ public final class FragmentedMp4Extractor {
...
@@ -172,16 +186,15 @@ public final class FragmentedMp4Extractor {
private
TrackFragment
fragmentRun
;
private
TrackFragment
fragmentRun
;
public
FragmentedMp4Extractor
()
{
public
FragmentedMp4Extractor
()
{
this
(
false
);
this
(
0
);
}
}
/**
/**
* @param enableSmoothStreamingWorkarounds Set to true if this extractor will be used to parse
* @param workaroundFlags Flags to allow parsing of faulty streams.
* SmoothStreaming streams. This will enable workarounds for SmoothStreaming violations of
* {@link #WORKAROUND_EVERY_VIDEO_FRAME_IS_SYNC_FRAME} is currently the only flag defined.
* the ISO base media file format (ISO 14496-12). Set to false otherwise.
*/
*/
public
FragmentedMp4Extractor
(
boolean
enableSmoothStreamingWorkaround
s
)
{
public
FragmentedMp4Extractor
(
int
workaroundFlag
s
)
{
this
.
enableSmoothStreamingWorkarounds
=
enableSmoothStreamingWorkaround
s
;
this
.
workaroundFlags
=
workaroundFlag
s
;
parserState
=
STATE_READING_ATOM_HEADER
;
parserState
=
STATE_READING_ATOM_HEADER
;
atomHeader
=
new
ParsableByteArray
(
ATOM_HEADER_SIZE
);
atomHeader
=
new
ParsableByteArray
(
ATOM_HEADER_SIZE
);
containerAtoms
=
new
Stack
<
ContainerAtom
>();
containerAtoms
=
new
Stack
<
ContainerAtom
>();
...
@@ -263,7 +276,8 @@ public final class FragmentedMp4Extractor {
...
@@ -263,7 +276,8 @@ public final class FragmentedMp4Extractor {
* in subsequent calls until the whole sample has been read.
* in subsequent calls until the whole sample has been read.
*
*
* @param inputStream The input stream from which data should be read.
* @param inputStream The input stream from which data should be read.
* @param out A {@link SampleHolder} into which the sample should be read.
* @param out A {@link SampleHolder} into which the next sample should be read. If null then
* {@link #RESULT_NEED_SAMPLE_HOLDER} will be returned once a sample has been reached.
* @return One or more of the {@code RESULT_*} flags defined in this class.
* @return One or more of the {@code RESULT_*} flags defined in this class.
* @throws ParserException If an error occurs parsing the media data.
* @throws ParserException If an error occurs parsing the media data.
*/
*/
...
@@ -466,7 +480,7 @@ public final class FragmentedMp4Extractor {
...
@@ -466,7 +480,7 @@ public final class FragmentedMp4Extractor {
private
void
onMoofContainerAtomRead
(
ContainerAtom
moof
)
{
private
void
onMoofContainerAtomRead
(
ContainerAtom
moof
)
{
fragmentRun
=
new
TrackFragment
();
fragmentRun
=
new
TrackFragment
();
parseMoof
(
track
,
extendsDefaults
,
moof
,
fragmentRun
,
enableSmoothStreamingWorkaround
s
);
parseMoof
(
track
,
extendsDefaults
,
moof
,
fragmentRun
,
workaroundFlag
s
);
sampleIndex
=
0
;
sampleIndex
=
0
;
lastSyncSampleIndex
=
0
;
lastSyncSampleIndex
=
0
;
pendingSeekSyncSampleIndex
=
0
;
pendingSeekSyncSampleIndex
=
0
;
...
@@ -572,11 +586,12 @@ public final class FragmentedMp4Extractor {
...
@@ -572,11 +586,12 @@ public final class FragmentedMp4Extractor {
int
childStartPosition
=
stsd
.
getPosition
();
int
childStartPosition
=
stsd
.
getPosition
();
int
childAtomSize
=
stsd
.
readInt
();
int
childAtomSize
=
stsd
.
readInt
();
int
childAtomType
=
stsd
.
readInt
();
int
childAtomType
=
stsd
.
readInt
();
if
(
childAtomType
==
Atom
.
TYPE_avc1
||
childAtomType
==
Atom
.
TYPE_encv
)
{
if
(
childAtomType
==
Atom
.
TYPE_avc1
||
childAtomType
==
Atom
.
TYPE_avc3
Pair
<
MediaFormat
,
TrackEncryptionBox
>
avc1
=
||
childAtomType
==
Atom
.
TYPE_encv
)
{
parseAvc1FromParent
(
stsd
,
childStartPosition
,
childAtomSize
);
Pair
<
MediaFormat
,
TrackEncryptionBox
>
avc
=
mediaFormat
=
avc1
.
first
;
parseAvcFromParent
(
stsd
,
childStartPosition
,
childAtomSize
);
trackEncryptionBoxes
[
i
]
=
avc1
.
second
;
mediaFormat
=
avc
.
first
;
trackEncryptionBoxes
[
i
]
=
avc
.
second
;
}
else
if
(
childAtomType
==
Atom
.
TYPE_mp4a
||
childAtomType
==
Atom
.
TYPE_enca
)
{
}
else
if
(
childAtomType
==
Atom
.
TYPE_mp4a
||
childAtomType
==
Atom
.
TYPE_enca
)
{
Pair
<
MediaFormat
,
TrackEncryptionBox
>
mp4a
=
Pair
<
MediaFormat
,
TrackEncryptionBox
>
mp4a
=
parseMp4aFromParent
(
stsd
,
childStartPosition
,
childAtomSize
);
parseMp4aFromParent
(
stsd
,
childStartPosition
,
childAtomSize
);
...
@@ -588,7 +603,7 @@ public final class FragmentedMp4Extractor {
...
@@ -588,7 +603,7 @@ public final class FragmentedMp4Extractor {
return
Pair
.
create
(
mediaFormat
,
trackEncryptionBoxes
);
return
Pair
.
create
(
mediaFormat
,
trackEncryptionBoxes
);
}
}
private
static
Pair
<
MediaFormat
,
TrackEncryptionBox
>
parseAvc
1
FromParent
(
ParsableByteArray
parent
,
private
static
Pair
<
MediaFormat
,
TrackEncryptionBox
>
parseAvcFromParent
(
ParsableByteArray
parent
,
int
position
,
int
size
)
{
int
position
,
int
size
)
{
parent
.
setPosition
(
position
+
ATOM_HEADER_SIZE
);
parent
.
setPosition
(
position
+
ATOM_HEADER_SIZE
);
...
@@ -695,7 +710,7 @@ public final class FragmentedMp4Extractor {
...
@@ -695,7 +710,7 @@ public final class FragmentedMp4Extractor {
int
childAtomSize
=
parent
.
readInt
();
int
childAtomSize
=
parent
.
readInt
();
int
childAtomType
=
parent
.
readInt
();
int
childAtomType
=
parent
.
readInt
();
if
(
childAtomType
==
Atom
.
TYPE_frma
)
{
if
(
childAtomType
==
Atom
.
TYPE_frma
)
{
parent
.
readInt
();
// dataFormat.
Expect TYPE_avc1 (video) or TYPE_mp4a (audio).
parent
.
readInt
();
// dataFormat.
}
else
if
(
childAtomType
==
Atom
.
TYPE_schm
)
{
}
else
if
(
childAtomType
==
Atom
.
TYPE_schm
)
{
parent
.
skip
(
4
);
parent
.
skip
(
4
);
parent
.
readInt
();
// schemeType. Expect cenc
parent
.
readInt
();
// schemeType. Expect cenc
...
@@ -774,11 +789,11 @@ public final class FragmentedMp4Extractor {
...
@@ -774,11 +789,11 @@ public final class FragmentedMp4Extractor {
}
}
private
static
void
parseMoof
(
Track
track
,
DefaultSampleValues
extendsDefaults
,
private
static
void
parseMoof
(
Track
track
,
DefaultSampleValues
extendsDefaults
,
ContainerAtom
moof
,
TrackFragment
out
,
boolean
enableSmoothStreamingWorkaround
s
)
{
ContainerAtom
moof
,
TrackFragment
out
,
int
workaroundFlag
s
)
{
// TODO: Consider checking that the sequence number returned by parseMfhd is as expected.
// TODO: Consider checking that the sequence number returned by parseMfhd is as expected.
parseMfhd
(
moof
.
getLeafAtomOfType
(
Atom
.
TYPE_mfhd
).
getData
());
parseMfhd
(
moof
.
getLeafAtomOfType
(
Atom
.
TYPE_mfhd
).
getData
());
parseTraf
(
track
,
extendsDefaults
,
moof
.
getContainerAtomOfType
(
Atom
.
TYPE_traf
),
parseTraf
(
track
,
extendsDefaults
,
moof
.
getContainerAtomOfType
(
Atom
.
TYPE_traf
),
out
,
enableSmoothStreamingWorkaround
s
);
out
,
workaroundFlag
s
);
}
}
/**
/**
...
@@ -796,7 +811,7 @@ public final class FragmentedMp4Extractor {
...
@@ -796,7 +811,7 @@ public final class FragmentedMp4Extractor {
* Parses a traf atom (defined in 14496-12).
* Parses a traf atom (defined in 14496-12).
*/
*/
private
static
void
parseTraf
(
Track
track
,
DefaultSampleValues
extendsDefaults
,
private
static
void
parseTraf
(
Track
track
,
DefaultSampleValues
extendsDefaults
,
ContainerAtom
traf
,
TrackFragment
out
,
boolean
enableSmoothStreamingWorkaround
s
)
{
ContainerAtom
traf
,
TrackFragment
out
,
int
workaroundFlag
s
)
{
LeafAtom
saiz
=
traf
.
getLeafAtomOfType
(
Atom
.
TYPE_saiz
);
LeafAtom
saiz
=
traf
.
getLeafAtomOfType
(
Atom
.
TYPE_saiz
);
if
(
saiz
!=
null
)
{
if
(
saiz
!=
null
)
{
parseSaiz
(
saiz
.
getData
(),
out
);
parseSaiz
(
saiz
.
getData
(),
out
);
...
@@ -809,8 +824,7 @@ public final class FragmentedMp4Extractor {
...
@@ -809,8 +824,7 @@ public final class FragmentedMp4Extractor {
out
.
setSampleDescriptionIndex
(
fragmentHeader
.
sampleDescriptionIndex
);
out
.
setSampleDescriptionIndex
(
fragmentHeader
.
sampleDescriptionIndex
);
LeafAtom
trun
=
traf
.
getLeafAtomOfType
(
Atom
.
TYPE_trun
);
LeafAtom
trun
=
traf
.
getLeafAtomOfType
(
Atom
.
TYPE_trun
);
parseTrun
(
track
,
fragmentHeader
,
decodeTime
,
enableSmoothStreamingWorkarounds
,
trun
.
getData
(),
parseTrun
(
track
,
fragmentHeader
,
decodeTime
,
workaroundFlags
,
trun
.
getData
(),
out
);
out
);
LeafAtom
uuid
=
traf
.
getLeafAtomOfType
(
Atom
.
TYPE_uuid
);
LeafAtom
uuid
=
traf
.
getLeafAtomOfType
(
Atom
.
TYPE_uuid
);
if
(
uuid
!=
null
)
{
if
(
uuid
!=
null
)
{
parseUuid
(
uuid
.
getData
(),
out
);
parseUuid
(
uuid
.
getData
(),
out
);
...
@@ -895,8 +909,7 @@ public final class FragmentedMp4Extractor {
...
@@ -895,8 +909,7 @@ public final class FragmentedMp4Extractor {
* @param out The {@TrackFragment} into which parsed data should be placed.
* @param out The {@TrackFragment} into which parsed data should be placed.
*/
*/
private
static
void
parseTrun
(
Track
track
,
DefaultSampleValues
defaultSampleValues
,
private
static
void
parseTrun
(
Track
track
,
DefaultSampleValues
defaultSampleValues
,
long
decodeTime
,
boolean
enableSmoothStreamingWorkarounds
,
ParsableByteArray
trun
,
long
decodeTime
,
int
workaroundFlags
,
ParsableByteArray
trun
,
TrackFragment
out
)
{
TrackFragment
out
)
{
trun
.
setPosition
(
ATOM_HEADER_SIZE
);
trun
.
setPosition
(
ATOM_HEADER_SIZE
);
int
fullAtom
=
trun
.
readInt
();
int
fullAtom
=
trun
.
readInt
();
int
version
=
parseFullAtomVersion
(
fullAtom
);
int
version
=
parseFullAtomVersion
(
fullAtom
);
...
@@ -926,6 +939,9 @@ public final class FragmentedMp4Extractor {
...
@@ -926,6 +939,9 @@ public final class FragmentedMp4Extractor {
long
timescale
=
track
.
timescale
;
long
timescale
=
track
.
timescale
;
long
cumulativeTime
=
decodeTime
;
long
cumulativeTime
=
decodeTime
;
boolean
workaroundEveryVideoFrameIsSyncFrame
=
track
.
type
==
Track
.
TYPE_VIDEO
&&
((
workaroundFlags
&
WORKAROUND_EVERY_VIDEO_FRAME_IS_SYNC_FRAME
)
==
WORKAROUND_EVERY_VIDEO_FRAME_IS_SYNC_FRAME
);
for
(
int
i
=
0
;
i
<
numberOfEntries
;
i
++)
{
for
(
int
i
=
0
;
i
<
numberOfEntries
;
i
++)
{
// Use trun values if present, otherwise tfhd, otherwise trex.
// Use trun values if present, otherwise tfhd, otherwise trex.
int
sampleDuration
=
sampleDurationsPresent
?
trun
.
readUnsignedIntToInt
()
int
sampleDuration
=
sampleDurationsPresent
?
trun
.
readUnsignedIntToInt
()
...
@@ -934,11 +950,14 @@ public final class FragmentedMp4Extractor {
...
@@ -934,11 +950,14 @@ public final class FragmentedMp4Extractor {
int
sampleFlags
=
(
i
==
0
&&
firstSampleFlagsPresent
)
?
firstSampleFlags
int
sampleFlags
=
(
i
==
0
&&
firstSampleFlagsPresent
)
?
firstSampleFlags
:
sampleFlagsPresent
?
trun
.
readInt
()
:
defaultSampleValues
.
flags
;
:
sampleFlagsPresent
?
trun
.
readInt
()
:
defaultSampleValues
.
flags
;
if
(
sampleCompositionTimeOffsetsPresent
)
{
if
(
sampleCompositionTimeOffsetsPresent
)
{
// Fragmented mp4 streams packaged for smooth streaming violate the BMFF spec by specifying
// the sample offset as a signed integer in conjunction with a box version of 0.
int
sampleOffset
;
int
sampleOffset
;
if
(
version
==
0
&&
!
enableSmoothStreamingWorkarounds
)
{
if
(
version
==
0
)
{
sampleOffset
=
trun
.
readUnsignedIntToInt
();
// The BMFF spec (ISO 14496-12) states that sample offsets should be unsigned integers in
// version 0 trun boxes, however a significant number of streams violate the spec and use
// signed integers instead. It's safe to always parse sample offsets as signed integers
// here, because unsigned integers will still be parsed correctly (unless their top bit is
// set, which is never true in practice because sample offsets are always small).
sampleOffset
=
trun
.
readInt
();
}
else
{
}
else
{
sampleOffset
=
trun
.
readInt
();
sampleOffset
=
trun
.
readInt
();
}
}
...
@@ -947,9 +966,7 @@ public final class FragmentedMp4Extractor {
...
@@ -947,9 +966,7 @@ public final class FragmentedMp4Extractor {
sampleDecodingTimeTable
[
i
]
=
(
int
)
((
cumulativeTime
*
1000
)
/
timescale
);
sampleDecodingTimeTable
[
i
]
=
(
int
)
((
cumulativeTime
*
1000
)
/
timescale
);
sampleSizeTable
[
i
]
=
sampleSize
;
sampleSizeTable
[
i
]
=
sampleSize
;
boolean
isSync
=
((
sampleFlags
>>
16
)
&
0x1
)
==
0
;
boolean
isSync
=
((
sampleFlags
>>
16
)
&
0x1
)
==
0
;
if
(
track
.
type
==
Track
.
TYPE_VIDEO
&&
enableSmoothStreamingWorkarounds
&&
i
!=
0
)
{
if
(
workaroundEveryVideoFrameIsSyncFrame
&&
i
!=
0
)
{
// Fragmented mp4 streams packaged for smooth streaming violate the BMFF spec by indicating
// that every sample is a sync frame, when this is not actually the case.
isSync
=
false
;
isSync
=
false
;
}
}
if
(
isSync
)
{
if
(
isSync
)
{
...
@@ -1130,6 +1147,9 @@ public final class FragmentedMp4Extractor {
...
@@ -1130,6 +1147,9 @@ public final class FragmentedMp4Extractor {
@SuppressLint
(
"InlinedApi"
)
@SuppressLint
(
"InlinedApi"
)
private
int
readSample
(
NonBlockingInputStream
inputStream
,
SampleHolder
out
)
{
private
int
readSample
(
NonBlockingInputStream
inputStream
,
SampleHolder
out
)
{
if
(
out
==
null
)
{
return
RESULT_NEED_SAMPLE_HOLDER
;
}
int
sampleSize
=
fragmentRun
.
sampleSizeTable
[
sampleIndex
];
int
sampleSize
=
fragmentRun
.
sampleSizeTable
[
sampleIndex
];
ByteBuffer
outputData
=
out
.
data
;
ByteBuffer
outputData
=
out
.
data
;
if
(
parserState
==
STATE_READING_SAMPLE_START
)
{
if
(
parserState
==
STATE_READING_SAMPLE_START
)
{
...
...
library/src/main/java/com/google/android/exoplayer/parser/webm/DefaultEbmlReader.java
0 → 100644
View file @
4228f2cf
/*
* 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
.
parser
.
webm
;
import
com.google.android.exoplayer.upstream.NonBlockingInputStream
;
import
com.google.android.exoplayer.util.Assertions
;
import
java.nio.ByteBuffer
;
import
java.nio.charset.Charset
;
import
java.util.Stack
;
/**
* Default version of a basic event-driven incremental EBML parser which needs an
* {@link EbmlEventHandler} to define IDs/types and react to events.
*
* <p>EBML can be summarized as a binary XML format somewhat similar to Protocol Buffers.
* It was originally designed for the Matroska container format. More information about EBML and
* Matroska is available <a href="http://www.matroska.org/technical/specs/index.html">here</a>.
*/
/* package */
final
class
DefaultEbmlReader
implements
EbmlReader
{
// State values used in variables state, elementIdState, elementContentSizeState, and
// varintBytesState.
private
static
final
int
STATE_BEGIN_READING
=
0
;
private
static
final
int
STATE_READ_CONTENTS
=
1
;
private
static
final
int
STATE_FINISHED_READING
=
2
;
/**
* The first byte of a variable-length integer (varint) will have one of these bit masks
* indicating the total length in bytes.
*
* <p>{@code 0x80} is a one-byte integer, {@code 0x40} is two bytes, and so on up to eight bytes.
*/
private
static
final
int
[]
VARINT_LENGTH_MASKS
=
new
int
[]
{
0x80
,
0x40
,
0x20
,
0x10
,
0x08
,
0x04
,
0x02
,
0x01
};
private
static
final
int
MAX_INTEGER_ELEMENT_SIZE_BYTES
=
8
;
private
static
final
int
VALID_FLOAT32_ELEMENT_SIZE_BYTES
=
4
;
private
static
final
int
VALID_FLOAT64_ELEMENT_SIZE_BYTES
=
8
;
/**
* Scratch space to read in EBML varints, unsigned ints, and floats - each of which can be
* up to 8 bytes.
*/
private
final
byte
[]
tempByteArray
=
new
byte
[
8
];
private
final
Stack
<
MasterElement
>
masterElementsStack
=
new
Stack
<
MasterElement
>();
/**
* Current {@link EbmlEventHandler} which is queried for element types
* and informed of element events.
*/
private
EbmlEventHandler
eventHandler
;
/**
* Overall state for the current element. Must be one of the {@code STATE_*} constants.
*/
private
int
state
;
/**
* Total bytes read since starting or the last {@link #reset()}.
*/
private
long
bytesRead
;
/**
* The starting byte offset of the current element being parsed.
*/
private
long
elementOffset
;
/**
* Holds the current element ID after {@link #elementIdState} is {@link #STATE_FINISHED_READING}.
*/
private
int
elementId
;
/**
* State for the ID of the current element. Must be one of the {@code STATE_*} constants.
*/
private
int
elementIdState
;
/**
* Holds the current element content size after {@link #elementContentSizeState}
* is {@link #STATE_FINISHED_READING}.
*/
private
long
elementContentSize
;
/**
* State for the content size of the current element.
* Must be one of the {@code STATE_*} constants.
*/
private
int
elementContentSizeState
;
/**
* State for the current variable-length integer (varint) being read into
* {@link #tempByteArray}. Must be one of the {@code STATE_*} constants.
*/
private
int
varintBytesState
;
/**
* Length in bytes of the current variable-length integer (varint) being read into
* {@link #tempByteArray}.
*/
private
int
varintBytesLength
;
/**
* Counts the number of bytes being contiguously read into either {@link #tempByteArray} or
* {@link #stringBytes}. Used to determine when all required bytes have been read across
* multiple calls.
*/
private
int
bytesState
;
/**
* Holds string element bytes as they're being read in. Allocated after the element content
* size is known and released after calling {@link EbmlEventHandler#onStringElement(int, String)}.
*/
private
byte
[]
stringBytes
;
@Override
public
void
setEventHandler
(
EbmlEventHandler
eventHandler
)
{
this
.
eventHandler
=
eventHandler
;
}
@Override
public
int
read
(
NonBlockingInputStream
inputStream
)
{
Assertions
.
checkState
(
eventHandler
!=
null
);
while
(
true
)
{
while
(!
masterElementsStack
.
isEmpty
()
&&
bytesRead
>=
masterElementsStack
.
peek
().
elementEndOffsetBytes
)
{
if
(!
eventHandler
.
onMasterElementEnd
(
masterElementsStack
.
pop
().
elementId
))
{
return
READ_RESULT_CONTINUE
;
}
}
if
(
state
==
STATE_BEGIN_READING
)
{
int
idResult
=
readElementId
(
inputStream
);
if
(
idResult
!=
READ_RESULT_CONTINUE
)
{
return
idResult
;
}
int
sizeResult
=
readElementContentSize
(
inputStream
);
if
(
sizeResult
!=
READ_RESULT_CONTINUE
)
{
return
sizeResult
;
}
state
=
STATE_READ_CONTENTS
;
bytesState
=
0
;
}
int
type
=
eventHandler
.
getElementType
(
elementId
);
switch
(
type
)
{
case
TYPE_MASTER:
int
masterHeaderSize
=
(
int
)
(
bytesRead
-
elementOffset
);
// Header size is 12 bytes max.
masterElementsStack
.
add
(
new
MasterElement
(
elementId
,
bytesRead
+
elementContentSize
));
if
(!
eventHandler
.
onMasterElementStart
(
elementId
,
elementOffset
,
masterHeaderSize
,
elementContentSize
))
{
prepareForNextElement
();
return
READ_RESULT_CONTINUE
;
}
break
;
case
TYPE_UNSIGNED_INT:
if
(
elementContentSize
>
MAX_INTEGER_ELEMENT_SIZE_BYTES
)
{
throw
new
IllegalStateException
(
"Invalid integer size "
+
elementContentSize
);
}
int
intResult
=
readBytesInternal
(
inputStream
,
tempByteArray
,
(
int
)
elementContentSize
);
if
(
intResult
!=
READ_RESULT_CONTINUE
)
{
return
intResult
;
}
long
intValue
=
getTempByteArrayValue
((
int
)
elementContentSize
,
false
);
if
(!
eventHandler
.
onIntegerElement
(
elementId
,
intValue
))
{
prepareForNextElement
();
return
READ_RESULT_CONTINUE
;
}
break
;
case
TYPE_FLOAT:
if
(
elementContentSize
!=
VALID_FLOAT32_ELEMENT_SIZE_BYTES
&&
elementContentSize
!=
VALID_FLOAT64_ELEMENT_SIZE_BYTES
)
{
throw
new
IllegalStateException
(
"Invalid float size "
+
elementContentSize
);
}
int
floatResult
=
readBytesInternal
(
inputStream
,
tempByteArray
,
(
int
)
elementContentSize
);
if
(
floatResult
!=
READ_RESULT_CONTINUE
)
{
return
floatResult
;
}
long
valueBits
=
getTempByteArrayValue
((
int
)
elementContentSize
,
false
);
double
floatValue
;
if
(
elementContentSize
==
VALID_FLOAT32_ELEMENT_SIZE_BYTES
)
{
floatValue
=
Float
.
intBitsToFloat
((
int
)
valueBits
);
}
else
{
floatValue
=
Double
.
longBitsToDouble
(
valueBits
);
}
if
(!
eventHandler
.
onFloatElement
(
elementId
,
floatValue
))
{
prepareForNextElement
();
return
READ_RESULT_CONTINUE
;
}
break
;
case
TYPE_STRING:
if
(
elementContentSize
>
Integer
.
MAX_VALUE
)
{
throw
new
IllegalStateException
(
"String element size "
+
elementContentSize
+
" is larger than MAX_INT"
);
}
if
(
stringBytes
==
null
)
{
stringBytes
=
new
byte
[(
int
)
elementContentSize
];
}
int
stringResult
=
readBytesInternal
(
inputStream
,
stringBytes
,
(
int
)
elementContentSize
);
if
(
stringResult
!=
READ_RESULT_CONTINUE
)
{
return
stringResult
;
}
String
stringValue
=
new
String
(
stringBytes
,
Charset
.
forName
(
"UTF-8"
));
stringBytes
=
null
;
if
(!
eventHandler
.
onStringElement
(
elementId
,
stringValue
))
{
prepareForNextElement
();
return
READ_RESULT_CONTINUE
;
}
break
;
case
TYPE_BINARY:
if
(
elementContentSize
>
Integer
.
MAX_VALUE
)
{
throw
new
IllegalStateException
(
"Binary element size "
+
elementContentSize
+
" is larger than MAX_INT"
);
}
if
(
inputStream
.
getAvailableByteCount
()
<
elementContentSize
)
{
return
READ_RESULT_NEED_MORE_DATA
;
}
int
binaryHeaderSize
=
(
int
)
(
bytesRead
-
elementOffset
);
// Header size is 12 bytes max.
boolean
keepGoing
=
eventHandler
.
onBinaryElement
(
elementId
,
elementOffset
,
binaryHeaderSize
,
(
int
)
elementContentSize
,
inputStream
);
long
expectedBytesRead
=
elementOffset
+
binaryHeaderSize
+
elementContentSize
;
if
(
expectedBytesRead
!=
bytesRead
)
{
throw
new
IllegalStateException
(
"Incorrect total bytes read. Expected "
+
expectedBytesRead
+
" but actually "
+
bytesRead
);
}
if
(!
keepGoing
)
{
prepareForNextElement
();
return
READ_RESULT_CONTINUE
;
}
break
;
case
TYPE_UNKNOWN:
if
(
elementContentSize
>
Integer
.
MAX_VALUE
)
{
throw
new
IllegalStateException
(
"Unknown element size "
+
elementContentSize
+
" is larger than MAX_INT"
);
}
int
skipResult
=
skipBytesInternal
(
inputStream
,
(
int
)
elementContentSize
);
if
(
skipResult
!=
READ_RESULT_CONTINUE
)
{
return
skipResult
;
}
break
;
default
:
throw
new
IllegalStateException
(
"Invalid element type "
+
type
);
}
prepareForNextElement
();
}
}
@Override
public
long
getBytesRead
()
{
return
bytesRead
;
}
@Override
public
void
reset
()
{
prepareForNextElement
();
masterElementsStack
.
clear
();
bytesRead
=
0
;
}
@Override
public
long
readVarint
(
NonBlockingInputStream
inputStream
)
{
varintBytesState
=
STATE_BEGIN_READING
;
int
result
=
readVarintBytes
(
inputStream
);
if
(
result
!=
READ_RESULT_CONTINUE
)
{
throw
new
IllegalStateException
(
"Couldn't read varint"
);
}
return
getTempByteArrayValue
(
varintBytesLength
,
true
);
}
@Override
public
void
readBytes
(
NonBlockingInputStream
inputStream
,
ByteBuffer
byteBuffer
,
int
totalBytes
)
{
bytesState
=
0
;
int
result
=
readBytesInternal
(
inputStream
,
byteBuffer
,
totalBytes
);
if
(
result
!=
READ_RESULT_CONTINUE
)
{
throw
new
IllegalStateException
(
"Couldn't read bytes into buffer"
);
}
}
@Override
public
void
readBytes
(
NonBlockingInputStream
inputStream
,
byte
[]
byteArray
,
int
totalBytes
)
{
bytesState
=
0
;
int
result
=
readBytesInternal
(
inputStream
,
byteArray
,
totalBytes
);
if
(
result
!=
READ_RESULT_CONTINUE
)
{
throw
new
IllegalStateException
(
"Couldn't read bytes into array"
);
}
}
@Override
public
void
skipBytes
(
NonBlockingInputStream
inputStream
,
int
totalBytes
)
{
bytesState
=
0
;
int
result
=
skipBytesInternal
(
inputStream
,
totalBytes
);
if
(
result
!=
READ_RESULT_CONTINUE
)
{
throw
new
IllegalStateException
(
"Couldn't skip bytes"
);
}
}
/**
* Resets the internal state of {@link #read(NonBlockingInputStream)} so that it can start
* reading a new element from scratch.
*/
private
void
prepareForNextElement
()
{
state
=
STATE_BEGIN_READING
;
elementIdState
=
STATE_BEGIN_READING
;
elementContentSizeState
=
STATE_BEGIN_READING
;
elementOffset
=
bytesRead
;
}
/**
* Reads an element ID such that reading can be stopped and started again in a later call
* if not enough bytes are available. Returns {@link #READ_RESULT_CONTINUE} if a full element ID
* has been read into {@link #elementId}. Reset {@link #elementIdState} to
* {@link #STATE_BEGIN_READING} before calling to indicate a new element ID should be read.
*
* @param inputStream The input stream from which an element ID should be read
* @return One of the {@code RESULT_*} flags defined in this class
*/
private
int
readElementId
(
NonBlockingInputStream
inputStream
)
{
if
(
elementIdState
==
STATE_FINISHED_READING
)
{
return
READ_RESULT_CONTINUE
;
}
if
(
elementIdState
==
STATE_BEGIN_READING
)
{
varintBytesState
=
STATE_BEGIN_READING
;
elementIdState
=
STATE_READ_CONTENTS
;
}
int
result
=
readVarintBytes
(
inputStream
);
if
(
result
!=
READ_RESULT_CONTINUE
)
{
return
result
;
}
// Element IDs are at most 4 bytes so cast to int now.
elementId
=
(
int
)
getTempByteArrayValue
(
varintBytesLength
,
false
);
elementIdState
=
STATE_FINISHED_READING
;
return
READ_RESULT_CONTINUE
;
}
/**
* Reads an element's content size such that reading can be stopped and started again in a later
* call if not enough bytes are available.
*
* <p>Returns {@link #READ_RESULT_CONTINUE} if an entire element size has been
* read into {@link #elementContentSize}. Reset {@link #elementContentSizeState} to
* {@link #STATE_BEGIN_READING} before calling to indicate a new element size should be read.
*
* @param inputStream The input stream from which an element size should be read
* @return One of the {@code RESULT_*} flags defined in this class
*/
private
int
readElementContentSize
(
NonBlockingInputStream
inputStream
)
{
if
(
elementContentSizeState
==
STATE_FINISHED_READING
)
{
return
READ_RESULT_CONTINUE
;
}
if
(
elementContentSizeState
==
STATE_BEGIN_READING
)
{
varintBytesState
=
STATE_BEGIN_READING
;
elementContentSizeState
=
STATE_READ_CONTENTS
;
}
int
result
=
readVarintBytes
(
inputStream
);
if
(
result
!=
READ_RESULT_CONTINUE
)
{
return
result
;
}
elementContentSize
=
getTempByteArrayValue
(
varintBytesLength
,
true
);
elementContentSizeState
=
STATE_FINISHED_READING
;
return
READ_RESULT_CONTINUE
;
}
/**
* Reads an EBML variable-length integer (varint) such that reading can be stopped and started
* again in a later call if not enough bytes are available.
*
* <p>Returns {@link #READ_RESULT_CONTINUE} if an entire varint has been read into
* {@link #tempByteArray} and the length of the varint is in {@link #varintBytesLength}.
* Reset {@link #varintBytesState} to {@link #STATE_BEGIN_READING} before calling to indicate
* a new varint should be read.
*
* @param inputStream The input stream from which a varint should be read
* @return One of the {@code RESULT_*} flags defined in this class
*/
private
int
readVarintBytes
(
NonBlockingInputStream
inputStream
)
{
if
(
varintBytesState
==
STATE_FINISHED_READING
)
{
return
READ_RESULT_CONTINUE
;
}
// Read first byte to get length.
if
(
varintBytesState
==
STATE_BEGIN_READING
)
{
bytesState
=
0
;
int
result
=
readBytesInternal
(
inputStream
,
tempByteArray
,
1
);
if
(
result
!=
READ_RESULT_CONTINUE
)
{
return
result
;
}
varintBytesState
=
STATE_READ_CONTENTS
;
int
firstByte
=
tempByteArray
[
0
]
&
0xff
;
varintBytesLength
=
-
1
;
for
(
int
i
=
0
;
i
<
VARINT_LENGTH_MASKS
.
length
;
i
++)
{
if
((
VARINT_LENGTH_MASKS
[
i
]
&
firstByte
)
!=
0
)
{
varintBytesLength
=
i
+
1
;
break
;
}
}
if
(
varintBytesLength
==
-
1
)
{
throw
new
IllegalStateException
(
"No valid varint length mask found at bytesRead = "
+
bytesRead
);
}
}
// Read remaining bytes.
int
result
=
readBytesInternal
(
inputStream
,
tempByteArray
,
varintBytesLength
);
if
(
result
!=
READ_RESULT_CONTINUE
)
{
return
result
;
}
// All bytes have been read.
return
READ_RESULT_CONTINUE
;
}
/**
* Reads a set amount of bytes into a {@link ByteBuffer} such that reading can be stopped
* and started again later if not enough bytes are available.
*
* <p>Returns {@link #READ_RESULT_CONTINUE} if all bytes have been read. Reset
* {@link #bytesState} to {@code 0} before calling to indicate a new set of bytes should be read.
*
* @param inputStream The input stream from which bytes should be read
* @param byteBuffer The {@link ByteBuffer} into which bytes should be read
* @param totalBytes The total size of bytes to be read
* @return One of the {@code RESULT_*} flags defined in this class
*/
private
int
readBytesInternal
(
NonBlockingInputStream
inputStream
,
ByteBuffer
byteBuffer
,
int
totalBytes
)
{
if
(
bytesState
==
STATE_BEGIN_READING
&&
totalBytes
>
byteBuffer
.
capacity
())
{
throw
new
IllegalArgumentException
(
"Byte buffer not large enough"
);
}
if
(
bytesState
>=
totalBytes
)
{
return
READ_RESULT_CONTINUE
;
}
int
remainingBytes
=
totalBytes
-
bytesState
;
int
additionalBytesRead
=
inputStream
.
read
(
byteBuffer
,
remainingBytes
);
return
updateBytesState
(
additionalBytesRead
,
totalBytes
);
}
/**
* Reads a set amount of bytes into a {@code byte[]} such that reading can be stopped
* and started again later if not enough bytes are available.
*
* <p>Returns {@link #READ_RESULT_CONTINUE} if all bytes have been read. Reset
* {@link #bytesState} to {@code 0} before calling to indicate a new set of bytes should be read.
*
* @param inputStream The input stream from which bytes should be read
* @param byteArray The {@code byte[]} into which bytes should be read
* @param totalBytes The total size of bytes to be read
* @return One of the {@code RESULT_*} flags defined in this class
*/
private
int
readBytesInternal
(
NonBlockingInputStream
inputStream
,
byte
[]
byteArray
,
int
totalBytes
)
{
if
(
bytesState
==
STATE_BEGIN_READING
&&
totalBytes
>
byteArray
.
length
)
{
throw
new
IllegalArgumentException
(
"Byte array not large enough"
);
}
if
(
bytesState
>=
totalBytes
)
{
return
READ_RESULT_CONTINUE
;
}
int
remainingBytes
=
totalBytes
-
bytesState
;
int
additionalBytesRead
=
inputStream
.
read
(
byteArray
,
bytesState
,
remainingBytes
);
return
updateBytesState
(
additionalBytesRead
,
totalBytes
);
}
/**
* Skips a set amount of bytes such that reading can be stopped and started again later if
* not enough bytes are available.
*
* <p>Returns {@link #READ_RESULT_CONTINUE} if all bytes have been skipped. Reset
* {@link #bytesState} to {@code 0} before calling to indicate a new set of bytes
* should be skipped.
*
* @param inputStream The input stream from which bytes should be skipped
* @param totalBytes The total size of bytes to be skipped
* @return One of the {@code RESULT_*} flags defined in this class
*/
private
int
skipBytesInternal
(
NonBlockingInputStream
inputStream
,
int
totalBytes
)
{
if
(
bytesState
>=
totalBytes
)
{
return
READ_RESULT_CONTINUE
;
}
int
remainingBytes
=
totalBytes
-
bytesState
;
int
additionalBytesRead
=
inputStream
.
skip
(
remainingBytes
);
return
updateBytesState
(
additionalBytesRead
,
totalBytes
);
}
/**
* Updates {@link #bytesState} and {@link #bytesRead} after reading bytes in one of the
* {@code verbBytesInternal} methods.
*
* @param additionalBytesRead The number of additional bytes read to be accounted for
* @param totalBytes The total size of bytes to be read or skipped
* @return One of the {@code RESULT_*} flags defined in this class
*/
private
int
updateBytesState
(
int
additionalBytesRead
,
int
totalBytes
)
{
if
(
additionalBytesRead
==
-
1
)
{
return
READ_RESULT_END_OF_FILE
;
}
bytesState
+=
additionalBytesRead
;
bytesRead
+=
additionalBytesRead
;
if
(
bytesState
<
totalBytes
)
{
return
READ_RESULT_NEED_MORE_DATA
;
}
else
{
return
READ_RESULT_CONTINUE
;
}
}
/**
* Parses and returns the integer value currently read into the first {@code byteLength} bytes
* of {@link #tempByteArray}. EBML varint length masks can optionally be removed.
*
* @param byteLength The number of bytes to parse from {@link #tempByteArray}
* @param removeLengthMask Removes the variable-length integer length mask from the value
* @return The resulting integer value. This value could be up to 8-bytes so a Java long is used
*/
private
long
getTempByteArrayValue
(
int
byteLength
,
boolean
removeLengthMask
)
{
if
(
removeLengthMask
)
{
tempByteArray
[
0
]
&=
~
VARINT_LENGTH_MASKS
[
varintBytesLength
-
1
];
}
long
varint
=
0
;
for
(
int
i
=
0
;
i
<
byteLength
;
i
++)
{
// Shift all existing bits up one byte and add the next byte at the bottom.
varint
=
(
varint
<<
8
)
|
(
tempByteArray
[
i
]
&
0xff
);
}
return
varint
;
}
/**
* Used in {@link #masterElementsStack} to track when the current master element ends so that
* {@link EbmlEventHandler#onMasterElementEnd(int)} is called.
*/
private
static
final
class
MasterElement
{
private
final
int
elementId
;
private
final
long
elementEndOffsetBytes
;
private
MasterElement
(
int
elementId
,
long
elementEndOffsetBytes
)
{
this
.
elementId
=
elementId
;
this
.
elementEndOffsetBytes
=
elementEndOffsetBytes
;
}
}
}
library/src/main/java/com/google/android/exoplayer/parser/webm/DefaultWebmExtractor.java
0 → 100644
View file @
4228f2cf
/*
* 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
.
parser
.
webm
;
import
com.google.android.exoplayer.MediaFormat
;
import
com.google.android.exoplayer.SampleHolder
;
import
com.google.android.exoplayer.parser.SegmentIndex
;
import
com.google.android.exoplayer.upstream.NonBlockingInputStream
;
import
com.google.android.exoplayer.util.LongArray
;
import
com.google.android.exoplayer.util.MimeTypes
;
import
android.annotation.TargetApi
;
import
android.media.MediaExtractor
;
import
java.util.Arrays
;
import
java.util.concurrent.TimeUnit
;
/**
* Default version of an extractor to facilitate data retrieval from the WebM container format.
*
* <p>WebM is a subset of the EBML elements defined for Matroska. More information about EBML and
* Matroska is available <a href="http://www.matroska.org/technical/specs/index.html">here</a>.
* More info about WebM is <a href="http://www.webmproject.org/code/specs/container/">here</a>.
*/
@TargetApi
(
16
)
public
final
class
DefaultWebmExtractor
implements
WebmExtractor
{
private
static
final
String
DOC_TYPE_WEBM
=
"webm"
;
private
static
final
String
CODEC_ID_VP9
=
"V_VP9"
;
private
static
final
int
UNKNOWN
=
-
1
;
// Element IDs
private
static
final
int
ID_EBML
=
0x1A45DFA3
;
private
static
final
int
ID_EBML_READ_VERSION
=
0x42F7
;
private
static
final
int
ID_DOC_TYPE
=
0x4282
;
private
static
final
int
ID_DOC_TYPE_READ_VERSION
=
0x4285
;
private
static
final
int
ID_SEGMENT
=
0x18538067
;
private
static
final
int
ID_INFO
=
0x1549A966
;
private
static
final
int
ID_TIMECODE_SCALE
=
0x2AD7B1
;
private
static
final
int
ID_DURATION
=
0x4489
;
private
static
final
int
ID_CLUSTER
=
0x1F43B675
;
private
static
final
int
ID_TIME_CODE
=
0xE7
;
private
static
final
int
ID_SIMPLE_BLOCK
=
0xA3
;
private
static
final
int
ID_TRACKS
=
0x1654AE6B
;
private
static
final
int
ID_TRACK_ENTRY
=
0xAE
;
private
static
final
int
ID_CODEC_ID
=
0x86
;
private
static
final
int
ID_VIDEO
=
0xE0
;
private
static
final
int
ID_PIXEL_WIDTH
=
0xB0
;
private
static
final
int
ID_PIXEL_HEIGHT
=
0xBA
;
private
static
final
int
ID_CUES
=
0x1C53BB6B
;
private
static
final
int
ID_CUE_POINT
=
0xBB
;
private
static
final
int
ID_CUE_TIME
=
0xB3
;
private
static
final
int
ID_CUE_TRACK_POSITIONS
=
0xB7
;
private
static
final
int
ID_CUE_CLUSTER_POSITION
=
0xF1
;
// SimpleBlock Lacing Values
private
static
final
int
LACING_NONE
=
0
;
private
static
final
int
LACING_XIPH
=
1
;
private
static
final
int
LACING_FIXED
=
2
;
private
static
final
int
LACING_EBML
=
3
;
private
final
EbmlReader
reader
;
private
final
byte
[]
simpleBlockTimecodeAndFlags
=
new
byte
[
3
];
private
SampleHolder
tempSampleHolder
;
private
boolean
sampleRead
;
private
boolean
prepared
=
false
;
private
long
segmentStartOffsetBytes
=
UNKNOWN
;
private
long
segmentEndOffsetBytes
=
UNKNOWN
;
private
long
timecodeScale
=
1000000L
;
private
long
durationUs
=
UNKNOWN
;
private
int
pixelWidth
=
UNKNOWN
;
private
int
pixelHeight
=
UNKNOWN
;
private
long
cuesSizeBytes
=
UNKNOWN
;
private
long
clusterTimecodeUs
=
UNKNOWN
;
private
long
simpleBlockTimecodeUs
=
UNKNOWN
;
private
MediaFormat
format
;
private
SegmentIndex
cues
;
private
LongArray
cueTimesUs
;
private
LongArray
cueClusterPositions
;
public
DefaultWebmExtractor
()
{
this
(
new
DefaultEbmlReader
());
}
/* package */
DefaultWebmExtractor
(
EbmlReader
reader
)
{
this
.
reader
=
reader
;
this
.
reader
.
setEventHandler
(
new
InnerEbmlEventHandler
());
this
.
cueTimesUs
=
new
LongArray
();
this
.
cueClusterPositions
=
new
LongArray
();
}
@Override
public
boolean
isPrepared
()
{
return
prepared
;
}
@Override
public
boolean
read
(
NonBlockingInputStream
inputStream
,
SampleHolder
sampleHolder
)
{
tempSampleHolder
=
sampleHolder
;
sampleRead
=
false
;
reader
.
read
(
inputStream
);
tempSampleHolder
=
null
;
return
sampleRead
;
}
@Override
public
boolean
seekTo
(
long
seekTimeUs
,
boolean
allowNoop
)
{
checkPrepared
();
if
(
allowNoop
&&
simpleBlockTimecodeUs
!=
UNKNOWN
&&
seekTimeUs
>=
simpleBlockTimecodeUs
)
{
int
clusterIndex
=
Arrays
.
binarySearch
(
cues
.
timesUs
,
clusterTimecodeUs
);
if
(
clusterIndex
>=
0
&&
seekTimeUs
<
clusterTimecodeUs
+
cues
.
durationsUs
[
clusterIndex
])
{
return
false
;
}
}
reader
.
reset
();
return
true
;
}
@Override
public
SegmentIndex
getCues
()
{
checkPrepared
();
return
cues
;
}
@Override
public
MediaFormat
getFormat
()
{
checkPrepared
();
return
format
;
}
/* package */
int
getElementType
(
int
id
)
{
switch
(
id
)
{
case
ID_EBML:
case
ID_SEGMENT:
case
ID_INFO:
case
ID_CLUSTER:
case
ID_TRACKS:
case
ID_TRACK_ENTRY:
case
ID_VIDEO:
case
ID_CUES:
case
ID_CUE_POINT:
case
ID_CUE_TRACK_POSITIONS:
return
EbmlReader
.
TYPE_MASTER
;
case
ID_EBML_READ_VERSION:
case
ID_DOC_TYPE_READ_VERSION:
case
ID_TIMECODE_SCALE:
case
ID_TIME_CODE:
case
ID_PIXEL_WIDTH:
case
ID_PIXEL_HEIGHT:
case
ID_CUE_TIME:
case
ID_CUE_CLUSTER_POSITION:
return
EbmlReader
.
TYPE_UNSIGNED_INT
;
case
ID_DOC_TYPE:
case
ID_CODEC_ID:
return
EbmlReader
.
TYPE_STRING
;
case
ID_SIMPLE_BLOCK:
return
EbmlReader
.
TYPE_BINARY
;
case
ID_DURATION:
return
EbmlReader
.
TYPE_FLOAT
;
default
:
return
EbmlReader
.
TYPE_UNKNOWN
;
}
}
/* package */
boolean
onMasterElementStart
(
int
id
,
long
elementOffsetBytes
,
int
headerSizeBytes
,
long
contentsSizeBytes
)
{
switch
(
id
)
{
case
ID_SEGMENT:
if
(
segmentStartOffsetBytes
!=
UNKNOWN
||
segmentEndOffsetBytes
!=
UNKNOWN
)
{
throw
new
IllegalStateException
(
"Multiple Segment elements not supported"
);
}
segmentStartOffsetBytes
=
elementOffsetBytes
+
headerSizeBytes
;
segmentEndOffsetBytes
=
elementOffsetBytes
+
headerSizeBytes
+
contentsSizeBytes
;
break
;
case
ID_CUES:
cuesSizeBytes
=
headerSizeBytes
+
contentsSizeBytes
;
break
;
default
:
// pass
}
return
true
;
}
/* package */
boolean
onMasterElementEnd
(
int
id
)
{
if
(
id
==
ID_CUES
)
{
finishPreparing
();
return
false
;
}
return
true
;
}
/* package */
boolean
onIntegerElement
(
int
id
,
long
value
)
{
switch
(
id
)
{
case
ID_EBML_READ_VERSION:
// Validate that EBMLReadVersion is supported. This extractor only supports v1.
if
(
value
!=
1
)
{
throw
new
IllegalArgumentException
(
"EBMLReadVersion "
+
value
+
" not supported"
);
}
break
;
case
ID_DOC_TYPE_READ_VERSION:
// Validate that DocTypeReadVersion is supported. This extractor only supports up to v2.
if
(
value
<
1
||
value
>
2
)
{
throw
new
IllegalArgumentException
(
"DocTypeReadVersion "
+
value
+
" not supported"
);
}
break
;
case
ID_TIMECODE_SCALE:
timecodeScale
=
value
;
break
;
case
ID_PIXEL_WIDTH:
pixelWidth
=
(
int
)
value
;
break
;
case
ID_PIXEL_HEIGHT:
pixelHeight
=
(
int
)
value
;
break
;
case
ID_CUE_TIME:
cueTimesUs
.
add
(
scaleTimecodeToUs
(
value
));
break
;
case
ID_CUE_CLUSTER_POSITION:
cueClusterPositions
.
add
(
value
);
break
;
case
ID_TIME_CODE:
clusterTimecodeUs
=
scaleTimecodeToUs
(
value
);
break
;
default
:
// pass
}
return
true
;
}
/* package */
boolean
onFloatElement
(
int
id
,
double
value
)
{
if
(
id
==
ID_DURATION
)
{
durationUs
=
scaleTimecodeToUs
((
long
)
value
);
}
return
true
;
}
/* package */
boolean
onStringElement
(
int
id
,
String
value
)
{
switch
(
id
)
{
case
ID_DOC_TYPE:
// Validate that DocType is supported. This extractor only supports "webm".
if
(!
DOC_TYPE_WEBM
.
equals
(
value
))
{
throw
new
IllegalArgumentException
(
"DocType "
+
value
+
" not supported"
);
}
break
;
case
ID_CODEC_ID:
// Validate that CodecID is supported. This extractor only supports "V_VP9".
if
(!
CODEC_ID_VP9
.
equals
(
value
))
{
throw
new
IllegalArgumentException
(
"CodecID "
+
value
+
" not supported"
);
}
break
;
default
:
// pass
}
return
true
;
}
/* package */
boolean
onBinaryElement
(
int
id
,
long
elementOffsetBytes
,
int
headerSizeBytes
,
int
contentsSizeBytes
,
NonBlockingInputStream
inputStream
)
{
if
(
id
==
ID_SIMPLE_BLOCK
)
{
// Please refer to http://www.matroska.org/technical/specs/index.html#simpleblock_structure
// for info about how data is organized in a SimpleBlock element.
// Value of trackNumber is not used but needs to be read.
reader
.
readVarint
(
inputStream
);
// Next three bytes have timecode and flags.
reader
.
readBytes
(
inputStream
,
simpleBlockTimecodeAndFlags
,
3
);
// First two bytes of the three are the relative timecode.
int
timecode
=
(
simpleBlockTimecodeAndFlags
[
0
]
<<
8
)
|
(
simpleBlockTimecodeAndFlags
[
1
]
&
0xff
);
long
timecodeUs
=
scaleTimecodeToUs
(
timecode
);
// Last byte of the three has some flags and the lacing value.
boolean
keyframe
=
(
simpleBlockTimecodeAndFlags
[
2
]
&
0x80
)
==
0x80
;
boolean
invisible
=
(
simpleBlockTimecodeAndFlags
[
2
]
&
0x08
)
==
0x08
;
int
lacing
=
(
simpleBlockTimecodeAndFlags
[
2
]
&
0x06
)
>>
1
;
// Validate lacing and set info into sample holder.
switch
(
lacing
)
{
case
LACING_NONE:
long
elementEndOffsetBytes
=
elementOffsetBytes
+
headerSizeBytes
+
contentsSizeBytes
;
simpleBlockTimecodeUs
=
clusterTimecodeUs
+
timecodeUs
;
tempSampleHolder
.
flags
=
keyframe
?
MediaExtractor
.
SAMPLE_FLAG_SYNC
:
0
;
tempSampleHolder
.
decodeOnly
=
invisible
;
tempSampleHolder
.
timeUs
=
clusterTimecodeUs
+
timecodeUs
;
tempSampleHolder
.
size
=
(
int
)
(
elementEndOffsetBytes
-
reader
.
getBytesRead
());
break
;
case
LACING_EBML:
case
LACING_FIXED:
case
LACING_XIPH:
default
:
throw
new
IllegalStateException
(
"Lacing mode "
+
lacing
+
" not supported"
);
}
// Read video data into sample holder.
reader
.
readBytes
(
inputStream
,
tempSampleHolder
.
data
,
tempSampleHolder
.
size
);
sampleRead
=
true
;
return
false
;
}
else
{
reader
.
skipBytes
(
inputStream
,
contentsSizeBytes
);
return
true
;
}
}
private
long
scaleTimecodeToUs
(
long
unscaledTimecode
)
{
return
TimeUnit
.
NANOSECONDS
.
toMicros
(
unscaledTimecode
*
timecodeScale
);
}
private
void
checkPrepared
()
{
if
(!
prepared
)
{
throw
new
IllegalStateException
(
"Parser not yet prepared"
);
}
}
private
void
finishPreparing
()
{
if
(
prepared
)
{
throw
new
IllegalStateException
(
"Already prepared"
);
}
else
if
(
segmentStartOffsetBytes
==
UNKNOWN
)
{
throw
new
IllegalStateException
(
"Segment start/end offsets unknown"
);
}
else
if
(
durationUs
==
UNKNOWN
)
{
throw
new
IllegalStateException
(
"Duration unknown"
);
}
else
if
(
pixelWidth
==
UNKNOWN
||
pixelHeight
==
UNKNOWN
)
{
throw
new
IllegalStateException
(
"Pixel width/height unknown"
);
}
else
if
(
cuesSizeBytes
==
UNKNOWN
)
{
throw
new
IllegalStateException
(
"Cues size unknown"
);
}
else
if
(
cueTimesUs
.
size
()
==
0
||
cueTimesUs
.
size
()
!=
cueClusterPositions
.
size
())
{
throw
new
IllegalStateException
(
"Invalid/missing cue points"
);
}
format
=
MediaFormat
.
createVideoFormat
(
MimeTypes
.
VIDEO_VP9
,
MediaFormat
.
NO_VALUE
,
pixelWidth
,
pixelHeight
,
null
);
int
cuePointsSize
=
cueTimesUs
.
size
();
int
[]
sizes
=
new
int
[
cuePointsSize
];
long
[]
offsets
=
new
long
[
cuePointsSize
];
long
[]
durationsUs
=
new
long
[
cuePointsSize
];
long
[]
timesUs
=
new
long
[
cuePointsSize
];
for
(
int
i
=
0
;
i
<
cuePointsSize
;
i
++)
{
timesUs
[
i
]
=
cueTimesUs
.
get
(
i
);
offsets
[
i
]
=
segmentStartOffsetBytes
+
cueClusterPositions
.
get
(
i
);
}
for
(
int
i
=
0
;
i
<
cuePointsSize
-
1
;
i
++)
{
sizes
[
i
]
=
(
int
)
(
offsets
[
i
+
1
]
-
offsets
[
i
]);
durationsUs
[
i
]
=
timesUs
[
i
+
1
]
-
timesUs
[
i
];
}
sizes
[
cuePointsSize
-
1
]
=
(
int
)
(
segmentEndOffsetBytes
-
offsets
[
cuePointsSize
-
1
]);
durationsUs
[
cuePointsSize
-
1
]
=
durationUs
-
timesUs
[
cuePointsSize
-
1
];
cues
=
new
SegmentIndex
((
int
)
cuesSizeBytes
,
sizes
,
offsets
,
durationsUs
,
timesUs
);
cueTimesUs
=
null
;
cueClusterPositions
=
null
;
prepared
=
true
;
}
/**
* Passes events through to {@link DefaultWebmExtractor} as
* callbacks from {@link EbmlReader} are received.
*/
private
final
class
InnerEbmlEventHandler
implements
EbmlEventHandler
{
@Override
public
int
getElementType
(
int
id
)
{
return
DefaultWebmExtractor
.
this
.
getElementType
(
id
);
}
@Override
public
boolean
onMasterElementStart
(
int
id
,
long
elementOffsetBytes
,
int
headerSizeBytes
,
long
contentsSizeBytes
)
{
return
DefaultWebmExtractor
.
this
.
onMasterElementStart
(
id
,
elementOffsetBytes
,
headerSizeBytes
,
contentsSizeBytes
);
}
@Override
public
boolean
onMasterElementEnd
(
int
id
)
{
return
DefaultWebmExtractor
.
this
.
onMasterElementEnd
(
id
);
}
@Override
public
boolean
onIntegerElement
(
int
id
,
long
value
)
{
return
DefaultWebmExtractor
.
this
.
onIntegerElement
(
id
,
value
);
}
@Override
public
boolean
onFloatElement
(
int
id
,
double
value
)
{
return
DefaultWebmExtractor
.
this
.
onFloatElement
(
id
,
value
);
}
@Override
public
boolean
onStringElement
(
int
id
,
String
value
)
{
return
DefaultWebmExtractor
.
this
.
onStringElement
(
id
,
value
);
}
@Override
public
boolean
onBinaryElement
(
int
id
,
long
elementOffsetBytes
,
int
headerSizeBytes
,
int
contentsSizeBytes
,
NonBlockingInputStream
inputStream
)
{
return
DefaultWebmExtractor
.
this
.
onBinaryElement
(
id
,
elementOffsetBytes
,
headerSizeBytes
,
contentsSizeBytes
,
inputStream
);
}
}
}
library/src/main/java/com/google/android/exoplayer/parser/webm/EbmlEventHandler.java
0 → 100644
View file @
4228f2cf
/*
* 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
.
parser
.
webm
;
import
com.google.android.exoplayer.upstream.NonBlockingInputStream
;
import
java.nio.ByteBuffer
;
/**
* Defines EBML element IDs/types and reacts to events.
*/
/* package */
interface
EbmlEventHandler
{
/**
* Retrieves the type of an element ID.
*
* <p>If {@link EbmlReader#TYPE_UNKNOWN} is returned then the element is skipped.
* Note that all children of a skipped master element are also skipped.
*
* @param id The integer ID of this element
* @return One of the {@code TYPE_} constants defined in this class
*/
public
int
getElementType
(
int
id
);
/**
* Called when a master element is encountered in the {@link NonBlockingInputStream}.
*
* <p>Following events should be considered as taking place "within" this element until a
* matching call to {@link #onMasterElementEnd(int)} is made. Note that it is possible for
* another master element of the same ID to be nested within itself.
*
* @param id The integer ID of this element
* @param elementOffsetBytes The byte offset where this element starts
* @param headerSizeBytes The byte length of this element's ID and size header
* @param contentsSizeBytes The byte length of this element's children
* @return {@code false} if parsing should stop right away
*/
public
boolean
onMasterElementStart
(
int
id
,
long
elementOffsetBytes
,
int
headerSizeBytes
,
long
contentsSizeBytes
);
/**
* Called when a master element has finished reading in all of its children from the
* {@link NonBlockingInputStream}.
*
* @param id The integer ID of this element
* @return {@code false} if parsing should stop right away
*/
public
boolean
onMasterElementEnd
(
int
id
);
/**
* Called when an integer element is encountered in the {@link NonBlockingInputStream}.
*
* @param id The integer ID of this element
* @param value The integer value this element contains
* @return {@code false} if parsing should stop right away
*/
public
boolean
onIntegerElement
(
int
id
,
long
value
);
/**
* Called when a float element is encountered in the {@link NonBlockingInputStream}.
*
* @param id The integer ID of this element
* @param value The float value this element contains
* @return {@code false} if parsing should stop right away
*/
public
boolean
onFloatElement
(
int
id
,
double
value
);
/**
* Called when a string element is encountered in the {@link NonBlockingInputStream}.
*
* @param id The integer ID of this element
* @param value The string value this element contains
* @return {@code false} if parsing should stop right away
*/
public
boolean
onStringElement
(
int
id
,
String
value
);
/**
* Called when a binary element is encountered in the {@link NonBlockingInputStream}.
*
* <p>The element header (containing element ID and content size) will already have been read.
* Subclasses must exactly read the entire contents of the element, which is
* {@code contentsSizeBytes} in length. It's guaranteed that the full element contents will be
* immediately available from {@code inputStream}.
*
* <p>Several methods in {@link EbmlReader} are available for reading the contents of a
* binary element:
* <ul>
* <li>{@link EbmlReader#readVarint(NonBlockingInputStream)}.
* <li>{@link EbmlReader#readBytes(NonBlockingInputStream, byte[], int)}.
* <li>{@link EbmlReader#readBytes(NonBlockingInputStream, ByteBuffer, int)}.
* <li>{@link EbmlReader#skipBytes(NonBlockingInputStream, int)}.
* <li>{@link EbmlReader#getBytesRead()}.
* </ul>
*
* @param id The integer ID of this element
* @param elementOffsetBytes The byte offset where this element starts
* @param headerSizeBytes The byte length of this element's ID and size header
* @param contentsSizeBytes The byte length of this element's contents
* @param inputStream The {@link NonBlockingInputStream} from which this
* element's contents should be read
* @return {@code false} if parsing should stop right away
*/
public
boolean
onBinaryElement
(
int
id
,
long
elementOffsetBytes
,
int
headerSizeBytes
,
int
contentsSizeBytes
,
NonBlockingInputStream
inputStream
);
}
library/src/main/java/com/google/android/exoplayer/parser/webm/EbmlReader.java
View file @
4228f2cf
...
@@ -16,528 +16,92 @@
...
@@ -16,528 +16,92 @@
package
com
.
google
.
android
.
exoplayer
.
parser
.
webm
;
package
com
.
google
.
android
.
exoplayer
.
parser
.
webm
;
import
com.google.android.exoplayer.upstream.NonBlockingInputStream
;
import
com.google.android.exoplayer.upstream.NonBlockingInputStream
;
import
com.google.android.exoplayer.util.Assertions
;
import
java.nio.ByteBuffer
;
import
java.nio.ByteBuffer
;
import
java.nio.charset.Charset
;
import
java.util.Stack
;
/**
/**
* An event-driven incremental EBML reader base class.
* Basic event-driven incremental EBML parser which needs an {@link EbmlEventHandler} to
* define IDs/types and react to events.
*
*
* <p>EBML can be summarized as a binary XML format somewhat similar to Protocol Buffers.
* <p>EBML can be summarized as a binary XML format somewhat similar to Protocol Buffers.
* It was originally designed for the Matroska container format. More information about EBML and
* It was originally designed for the Matroska container format. More information about EBML and
* Matroska is available <a href="http://www.matroska.org/technical/specs/index.html">here</a>.
* Matroska is available <a href="http://www.matroska.org/technical/specs/index.html">here</a>.
*/
*/
public
abstract
class
EbmlReader
{
/* package */
interface
EbmlReader
{
// Element Types
// Element Types
protected
static
final
int
TYPE_UNKNOWN
=
0
;
// Undefined element.
/** Undefined element. */
protected
static
final
int
TYPE_MASTER
=
1
;
// Contains child elements.
public
static
final
int
TYPE_UNKNOWN
=
0
;
protected
static
final
int
TYPE_UNSIGNED_INT
=
2
;
/** Contains child elements. */
protected
static
final
int
TYPE_STRING
=
3
;
public
static
final
int
TYPE_MASTER
=
1
;
protected
static
final
int
TYPE_BINARY
=
4
;
/** Unsigned integer value of up to 8 bytes. */
protected
static
final
int
TYPE_FLOAT
=
5
;
public
static
final
int
TYPE_UNSIGNED_INT
=
2
;
public
static
final
int
TYPE_STRING
=
3
;
public
static
final
int
TYPE_BINARY
=
4
;
/** IEEE floating point value of either 4 or 8 bytes. */
public
static
final
int
TYPE_FLOAT
=
5
;
// Return values for
methods read, readElementId, readElementSize, readVarintBytes, and readByte
s.
// Return values for
reading method
s.
p
rotected
static
final
int
RESULT_CONTINUE
=
0
;
p
ublic
static
final
int
READ_
RESULT_CONTINUE
=
0
;
p
rotected
static
final
int
RESULT_NEED_MORE_DATA
=
1
;
p
ublic
static
final
int
READ_
RESULT_NEED_MORE_DATA
=
1
;
p
rotected
static
final
int
RESULT_END_OF_FILE
=
2
;
p
ublic
static
final
int
READ_
RESULT_END_OF_FILE
=
2
;
// State values used in variables state, elementIdState, elementContentSizeState, and
public
void
setEventHandler
(
EbmlEventHandler
eventHandler
);
// varintBytesState.
private
static
final
int
STATE_BEGIN_READING
=
0
;
private
static
final
int
STATE_READ_CONTENTS
=
1
;
private
static
final
int
STATE_FINISHED_READING
=
2
;
/**
* The first byte of a variable-length integer (varint) will have one of these bit masks
* indicating the total length in bytes. {@code 0x80} is a one-byte integer,
* {@code 0x40} is two bytes, and so on up to eight bytes.
*/
private
static
final
int
[]
VARINT_LENGTH_MASKS
=
new
int
[]
{
0x80
,
0x40
,
0x20
,
0x10
,
0x08
,
0x04
,
0x02
,
0x01
};
private
final
Stack
<
MasterElement
>
masterElementsStack
=
new
Stack
<
MasterElement
>();
private
final
byte
[]
tempByteArray
=
new
byte
[
8
];
private
int
state
;
private
long
bytesRead
;
private
long
elementOffset
;
private
int
elementId
;
private
int
elementIdState
;
private
long
elementContentSize
;
private
int
elementContentSizeState
;
private
int
varintBytesState
;
private
int
varintBytesLength
;
private
int
bytesState
;
private
byte
[]
stringBytes
;
/**
* Called to retrieve the type of an element ID. If {@link #TYPE_UNKNOWN} is returned then
* the element is skipped. Note that all children of a skipped master element are also skipped.
*
* @param id The integer ID of this element.
* @return One of the {@code TYPE_} constants defined in this class.
*/
protected
abstract
int
getElementType
(
int
id
);
/**
* Called when a master element is encountered in the {@link NonBlockingInputStream}.
* Following events should be considered as taking place "within" this element until a
* matching call to {@link #onMasterElementEnd(int)} is made. Note that it
* is possible for the same master element to be nested within itself.
*
* @param id The integer ID of this element.
* @param elementOffset The byte offset where this element starts.
* @param headerSize The byte length of this element's ID and size header.
* @param contentsSize The byte length of this element's children.
* @return {@code true} if parsing should continue or {@code false} if it should stop right away.
*/
protected
abstract
boolean
onMasterElementStart
(
int
id
,
long
elementOffset
,
int
headerSize
,
int
contentsSize
);
/**
* Called when a master element has finished reading in all of its children from the
* {@link NonBlockingInputStream}.
*
* @param id The integer ID of this element.
* @return {@code true} if parsing should continue or {@code false} if it should stop right away.
*/
protected
abstract
boolean
onMasterElementEnd
(
int
id
);
/**
* Called when an integer element is encountered in the {@link NonBlockingInputStream}.
*
* @param id The integer ID of this element.
* @param value The integer value this element contains.
* @return {@code true} if parsing should continue or {@code false} if it should stop right away.
*/
protected
abstract
boolean
onIntegerElement
(
int
id
,
long
value
);
/**
* Called when a float element is encountered in the {@link NonBlockingInputStream}.
*
* @param id The integer ID of this element.
* @param value The float value this element contains.
* @return {@code true} if parsing should continue or {@code false} if it should stop right away.
*/
protected
abstract
boolean
onFloatElement
(
int
id
,
double
value
);
/**
* Called when a string element is encountered in the {@link NonBlockingInputStream}.
*
* @param id The integer ID of this element.
* @param value The string value this element contains.
* @return {@code true} if parsing should continue or {@code false} if it should stop right away.
*/
protected
abstract
boolean
onStringElement
(
int
id
,
String
value
);
/**
* Called when a binary element is encountered in the {@link NonBlockingInputStream}.
* The element header (containing element ID and content size) will already have been read.
* Subclasses must exactly read the entire contents of the element, which is {@code contentsSize}
* bytes in length. It's guaranteed that the full element contents will be immediately available
* from {@code inputStream}.
*
* <p>Several methods are available for reading the contents of a binary element:
* <ul>
* <li>{@link #readVarint(NonBlockingInputStream)}.
* <li>{@link #readBytes(NonBlockingInputStream, byte[], int)}.
* <li>{@link #readBytes(NonBlockingInputStream, ByteBuffer, int)}.
* <li>{@link #skipBytes(NonBlockingInputStream, int)}.
* <li>{@link #getBytesRead()}.
*
* @param inputStream The {@link NonBlockingInputStream} from which this
* element's contents should be read.
* @param id The integer ID of this element.
* @param elementOffset The byte offset where this element starts.
* @param headerSize The byte length of this element's ID and size header.
* @param contentsSize The byte length of this element's contents.
* @return {@code true} if parsing should continue or {@code false} if it should stop right away.
*/
protected
abstract
boolean
onBinaryElement
(
NonBlockingInputStream
inputStream
,
int
id
,
long
elementOffset
,
int
headerSize
,
int
contentsSize
);
/**
/**
* Reads from a {@link NonBlockingInputStream} and calls event callbacks as needed.
* Reads from a {@link NonBlockingInputStream} and calls event callbacks as needed.
*
*
* @param inputStream The input stream from which data should be read
.
* @param inputStream The input stream from which data should be read
* @return One of the {@code RESULT_*} flags defined in this
class.
* @return One of the {@code RESULT_*} flags defined in this
interface
*/
*/
protected
final
int
read
(
NonBlockingInputStream
inputStream
)
{
public
int
read
(
NonBlockingInputStream
inputStream
);
while
(
true
)
{
while
(
masterElementsStack
.
size
()
>
0
&&
bytesRead
>=
masterElementsStack
.
peek
().
elementEndOffset
)
{
if
(!
onMasterElementEnd
(
masterElementsStack
.
pop
().
elementId
))
{
return
RESULT_CONTINUE
;
}
}
if
(
state
==
STATE_BEGIN_READING
)
{
final
int
resultId
=
readElementId
(
inputStream
);
if
(
resultId
!=
RESULT_CONTINUE
)
{
return
resultId
;
}
final
int
resultSize
=
readElementContentSize
(
inputStream
);
if
(
resultSize
!=
RESULT_CONTINUE
)
{
return
resultSize
;
}
state
=
STATE_READ_CONTENTS
;
bytesState
=
0
;
}
final
int
type
=
getElementType
(
elementId
);
switch
(
type
)
{
case
TYPE_MASTER:
final
int
masterHeaderSize
=
(
int
)
(
bytesRead
-
elementOffset
);
masterElementsStack
.
add
(
new
MasterElement
(
elementId
,
bytesRead
+
elementContentSize
));
if
(!
onMasterElementStart
(
elementId
,
elementOffset
,
masterHeaderSize
,
(
int
)
elementContentSize
))
{
prepareForNextElement
();
return
RESULT_CONTINUE
;
}
break
;
case
TYPE_UNSIGNED_INT:
Assertions
.
checkState
(
elementContentSize
<=
8
);
final
int
resultInt
=
readBytes
(
inputStream
,
null
,
tempByteArray
,
(
int
)
elementContentSize
);
if
(
resultInt
!=
RESULT_CONTINUE
)
{
return
resultInt
;
}
final
long
intValue
=
parseTempByteArray
((
int
)
elementContentSize
,
false
);
if
(!
onIntegerElement
(
elementId
,
intValue
))
{
prepareForNextElement
();
return
RESULT_CONTINUE
;
}
break
;
case
TYPE_FLOAT:
Assertions
.
checkState
(
elementContentSize
==
4
||
elementContentSize
==
8
);
final
int
resultFloat
=
readBytes
(
inputStream
,
null
,
tempByteArray
,
(
int
)
elementContentSize
);
if
(
resultFloat
!=
RESULT_CONTINUE
)
{
return
resultFloat
;
}
final
long
valueBits
=
parseTempByteArray
((
int
)
elementContentSize
,
false
);
final
double
floatValue
;
if
(
elementContentSize
==
4
)
{
floatValue
=
Float
.
intBitsToFloat
((
int
)
valueBits
);
}
else
{
floatValue
=
Double
.
longBitsToDouble
(
valueBits
);
}
if
(!
onFloatElement
(
elementId
,
floatValue
))
{
prepareForNextElement
();
return
RESULT_CONTINUE
;
}
break
;
case
TYPE_STRING:
if
(
stringBytes
==
null
)
{
stringBytes
=
new
byte
[(
int
)
elementContentSize
];
}
final
int
resultString
=
readBytes
(
inputStream
,
null
,
stringBytes
,
(
int
)
elementContentSize
);
if
(
resultString
!=
RESULT_CONTINUE
)
{
return
resultString
;
}
final
String
stringValue
=
new
String
(
stringBytes
,
Charset
.
forName
(
"UTF-8"
));
stringBytes
=
null
;
if
(!
onStringElement
(
elementId
,
stringValue
))
{
prepareForNextElement
();
return
RESULT_CONTINUE
;
}
break
;
case
TYPE_BINARY:
if
(
inputStream
.
getAvailableByteCount
()
<
elementContentSize
)
{
return
RESULT_NEED_MORE_DATA
;
}
final
int
binaryHeaderSize
=
(
int
)
(
bytesRead
-
elementOffset
);
final
boolean
keepGoing
=
onBinaryElement
(
inputStream
,
elementId
,
elementOffset
,
binaryHeaderSize
,
(
int
)
elementContentSize
);
Assertions
.
checkState
(
elementOffset
+
binaryHeaderSize
+
elementContentSize
==
bytesRead
);
if
(!
keepGoing
)
{
prepareForNextElement
();
return
RESULT_CONTINUE
;
}
break
;
case
TYPE_UNKNOWN:
// Unknown elements should be skipped.
Assertions
.
checkState
(
readBytes
(
inputStream
,
null
,
null
,
(
int
)
elementContentSize
)
==
RESULT_CONTINUE
);
break
;
default
:
throw
new
IllegalStateException
(
"Invalid element type "
+
type
);
}
prepareForNextElement
();
}
}
/**
/**
* @return The total number of bytes consumed by the reader since first created
* The total number of bytes consumed by the reader since first created or last {@link #reset()}.
* or last {@link #reset()}.
*/
*/
protected
final
long
getBytesRead
()
{
public
long
getBytesRead
();
return
bytesRead
;
}
/**
/**
* Resets the entire state of the reader so that it will read a new EBML structure from scratch.
* Resets the entire state of the reader so that it will read a new EBML structure from scratch.
* This includes resetting {@link #bytesRead} back to 0 and discarding all pending
*
* {@link #onMasterElementEnd(int)} events.
* <p>This includes resetting the value returned from {@link #getBytesRead()} to 0 and discarding
* all pending {@link EbmlEventHandler#onMasterElementEnd(int)} events.
*/
*/
protected
final
void
reset
()
{
public
void
reset
();
prepareForNextElement
();
masterElementsStack
.
clear
();
bytesRead
=
0
;
}
/**
/**
* Reads, parses, and returns an EBML variable-length integer (varint) from the contents
* Reads, parses, and returns an EBML variable-length integer (varint) from the contents
* of a binary element.
* of a binary element.
*
*
* @param inputStream The input stream from which data should be read
.
* @param inputStream The input stream from which data should be read
* @return The varint value at the current position of the contents of a binary element
.
* @return The varint value at the current position of the contents of a binary element
*/
*/
protected
final
long
readVarint
(
NonBlockingInputStream
inputStream
)
{
public
long
readVarint
(
NonBlockingInputStream
inputStream
);
varintBytesState
=
STATE_BEGIN_READING
;
Assertions
.
checkState
(
readVarintBytes
(
inputStream
)
==
RESULT_CONTINUE
);
return
parseTempByteArray
(
varintBytesLength
,
true
);
}
/**
/**
* Reads a fixed number of bytes from the contents of a binary element into a {@link ByteBuffer}.
* Reads a fixed number of bytes from the contents of a binary element into a {@link ByteBuffer}.
*
*
* @param inputStream The input stream from which data should be read
.
* @param inputStream The input stream from which data should be read
* @param byteBuffer The {@link ByteBuffer} to which data should be written
.
* @param byteBuffer The {@link ByteBuffer} to which data should be written
* @param totalBytes The fixed number of bytes to be read and written
.
* @param totalBytes The fixed number of bytes to be read and written
*/
*/
protected
final
void
readBytes
(
public
void
readBytes
(
NonBlockingInputStream
inputStream
,
ByteBuffer
byteBuffer
,
int
totalBytes
);
NonBlockingInputStream
inputStream
,
ByteBuffer
byteBuffer
,
int
totalBytes
)
{
bytesState
=
0
;
Assertions
.
checkState
(
readBytes
(
inputStream
,
byteBuffer
,
null
,
totalBytes
)
==
RESULT_CONTINUE
);
}
/**
/**
* Reads a fixed number of bytes from the contents of a binary element into a {@code byte[]}.
* Reads a fixed number of bytes from the contents of a binary element into a {@code byte[]}.
*
*
* @param inputStream The input stream from which data should be read
.
* @param inputStream The input stream from which data should be read
* @param byteArray The byte array to which data should be written
.
* @param byteArray The byte array to which data should be written
* @param totalBytes The fixed number of bytes to be read and written
.
* @param totalBytes The fixed number of bytes to be read and written
*/
*/
protected
final
void
readBytes
(
public
void
readBytes
(
NonBlockingInputStream
inputStream
,
byte
[]
byteArray
,
int
totalBytes
);
NonBlockingInputStream
inputStream
,
byte
[]
byteArray
,
int
totalBytes
)
{
bytesState
=
0
;
Assertions
.
checkState
(
readBytes
(
inputStream
,
null
,
byteArray
,
totalBytes
)
==
RESULT_CONTINUE
);
}
/**
/**
* Skips a fixed number of bytes from the contents of a binary element.
* Skips a fixed number of bytes from the contents of a binary element.
*
*
* @param inputStream The input stream from which data should be skipped.
* @param inputStream The input stream from which data should be skipped
* @param totalBytes The fixed number of bytes to be skipped.
* @param totalBytes The fixed number of bytes to be skipped
*/
protected
final
void
skipBytes
(
NonBlockingInputStream
inputStream
,
int
totalBytes
)
{
bytesState
=
0
;
Assertions
.
checkState
(
readBytes
(
inputStream
,
null
,
null
,
totalBytes
)
==
RESULT_CONTINUE
);
}
/**
* Resets the internal state of {@link #read(NonBlockingInputStream)} so that it can start
* reading a new element from scratch.
*/
private
final
void
prepareForNextElement
()
{
state
=
STATE_BEGIN_READING
;
elementIdState
=
STATE_BEGIN_READING
;
elementContentSizeState
=
STATE_BEGIN_READING
;
elementOffset
=
bytesRead
;
}
/**
* Reads an element ID such that reading can be stopped and started again in a later call
* if not enough bytes are available. Returns {@link #RESULT_CONTINUE} if a full element ID
* has been read into {@link #elementId}. Reset {@link #elementIdState} to
* {@link #STATE_BEGIN_READING} before calling to indicate a new element ID should be read.
*
* @param inputStream The input stream from which an element ID should be read.
* @return One of the {@code RESULT_*} flags defined in this class.
*/
private
int
readElementId
(
NonBlockingInputStream
inputStream
)
{
if
(
elementIdState
==
STATE_FINISHED_READING
)
{
return
RESULT_CONTINUE
;
}
if
(
elementIdState
==
STATE_BEGIN_READING
)
{
varintBytesState
=
STATE_BEGIN_READING
;
elementIdState
=
STATE_READ_CONTENTS
;
}
final
int
result
=
readVarintBytes
(
inputStream
);
if
(
result
!=
RESULT_CONTINUE
)
{
return
result
;
}
elementId
=
(
int
)
parseTempByteArray
(
varintBytesLength
,
false
);
elementIdState
=
STATE_FINISHED_READING
;
return
RESULT_CONTINUE
;
}
/**
* Reads an element's content size such that reading can be stopped and started again in a later
* call if not enough bytes are available. Returns {@link #RESULT_CONTINUE} if an entire element
* size has been read into {@link #elementContentSize}. Reset {@link #elementContentSizeState} to
* {@link #STATE_BEGIN_READING} before calling to indicate a new element size should be read.
*
* @param inputStream The input stream from which an element size should be read.
* @return One of the {@code RESULT_*} flags defined in this class.
*/
private
int
readElementContentSize
(
NonBlockingInputStream
inputStream
)
{
if
(
elementContentSizeState
==
STATE_FINISHED_READING
)
{
return
RESULT_CONTINUE
;
}
if
(
elementContentSizeState
==
STATE_BEGIN_READING
)
{
varintBytesState
=
STATE_BEGIN_READING
;
elementContentSizeState
=
STATE_READ_CONTENTS
;
}
final
int
result
=
readVarintBytes
(
inputStream
);
if
(
result
!=
RESULT_CONTINUE
)
{
return
result
;
}
elementContentSize
=
parseTempByteArray
(
varintBytesLength
,
true
);
elementContentSizeState
=
STATE_FINISHED_READING
;
return
RESULT_CONTINUE
;
}
/**
* Reads an EBML variable-length integer (varint) such that reading can be stopped and started
* again in a later call if not enough bytes are available. Returns {@link #RESULT_CONTINUE} if
* an entire varint has been read into {@link #tempByteArray} and the length of the varint is in
* {@link #varintBytesLength}. Reset {@link #varintBytesState} to {@link #STATE_BEGIN_READING}
* before calling to indicate a new varint should be read.
*
* @param inputStream The input stream from which a varint should be read.
* @return One of the {@code RESULT_*} flags defined in this class.
*/
*/
private
int
readVarintBytes
(
NonBlockingInputStream
inputStream
)
{
public
void
skipBytes
(
NonBlockingInputStream
inputStream
,
int
totalBytes
);
if
(
varintBytesState
==
STATE_FINISHED_READING
)
{
return
RESULT_CONTINUE
;
}
// Read first byte to get length.
if
(
varintBytesState
==
STATE_BEGIN_READING
)
{
bytesState
=
0
;
final
int
result
=
readBytes
(
inputStream
,
null
,
tempByteArray
,
1
);
if
(
result
!=
RESULT_CONTINUE
)
{
return
result
;
}
varintBytesState
=
STATE_READ_CONTENTS
;
final
int
firstByte
=
tempByteArray
[
0
]
&
0xff
;
varintBytesLength
=
-
1
;
for
(
int
i
=
0
;
i
<
VARINT_LENGTH_MASKS
.
length
;
i
++)
{
if
((
VARINT_LENGTH_MASKS
[
i
]
&
firstByte
)
!=
0
)
{
varintBytesLength
=
i
+
1
;
break
;
}
}
if
(
varintBytesLength
==
-
1
)
{
throw
new
IllegalStateException
(
"No valid varint length mask found at bytesRead = "
+
bytesRead
);
}
}
// Read remaining bytes.
final
int
result
=
readBytes
(
inputStream
,
null
,
tempByteArray
,
varintBytesLength
);
if
(
result
!=
RESULT_CONTINUE
)
{
return
result
;
}
// All bytes have been read.
return
RESULT_CONTINUE
;
}
/**
* Reads a set amount of bytes into a {@link ByteBuffer}, {@code byte[]}, or nowhere (skipping
* the bytes) such that reading can be stopped and started again later if not enough bytes are
* available. Returns {@link #RESULT_CONTINUE} if all bytes have been read. Reset
* {@link #bytesState} to {@code 0} before calling to indicate a new set of bytes should be read.
*
* <p>If both {@code byteBuffer} and {@code byteArray} are not null then bytes are only read
* into {@code byteBuffer}.
*
* @param inputStream The input stream from which bytes should be read.
* @param byteBuffer The optional {@link ByteBuffer} into which bytes should be read.
* @param byteArray The optional {@code byte[]} into which bytes should be read.
* @param totalBytes The total size of bytes to be read or skipped.
* @return One of the {@code RESULT_*} flags defined in this class.
*/
private
int
readBytes
(
NonBlockingInputStream
inputStream
,
ByteBuffer
byteBuffer
,
byte
[]
byteArray
,
int
totalBytes
)
{
if
(
bytesState
==
STATE_BEGIN_READING
&&
((
byteBuffer
!=
null
&&
totalBytes
>
byteBuffer
.
capacity
())
||
(
byteArray
!=
null
&&
totalBytes
>
byteArray
.
length
)))
{
throw
new
IllegalStateException
(
"Byte destination not large enough"
);
}
if
(
bytesState
<
totalBytes
)
{
final
int
remainingBytes
=
totalBytes
-
bytesState
;
final
int
result
;
if
(
byteBuffer
!=
null
)
{
result
=
inputStream
.
read
(
byteBuffer
,
remainingBytes
);
}
else
if
(
byteArray
!=
null
)
{
result
=
inputStream
.
read
(
byteArray
,
bytesState
,
remainingBytes
);
}
else
{
result
=
inputStream
.
skip
(
remainingBytes
);
}
if
(
result
==
-
1
)
{
return
RESULT_END_OF_FILE
;
}
bytesState
+=
result
;
bytesRead
+=
result
;
if
(
bytesState
<
totalBytes
)
{
return
RESULT_NEED_MORE_DATA
;
}
}
return
RESULT_CONTINUE
;
}
/**
* Parses and returns the integer value currently read into the first {@code byteLength} bytes
* of {@link #tempByteArray}. EBML varint length masks can optionally be removed.
*
* @param byteLength The number of bytes to parse from {@link #tempByteArray}.
* @param removeLengthMask Removes the variable-length integer length mask from the value.
* @return The resulting integer value. This value could be up to 8-bytes so a Java long is used.
*/
private
long
parseTempByteArray
(
int
byteLength
,
boolean
removeLengthMask
)
{
if
(
removeLengthMask
)
{
tempByteArray
[
0
]
&=
~
VARINT_LENGTH_MASKS
[
varintBytesLength
-
1
];
}
long
varint
=
0
;
for
(
int
i
=
0
;
i
<
byteLength
;
i
++)
{
// Shift all existing bits up one byte and add the next byte at the bottom.
varint
=
(
varint
<<
8
)
|
(
tempByteArray
[
i
]
&
0xff
);
}
return
varint
;
}
/**
* Used in {@link #masterElementsStack} to track when the current master element ends so that
* {@link #onMasterElementEnd(int)} is called.
*/
private
static
final
class
MasterElement
{
private
final
int
elementId
;
private
final
long
elementEndOffset
;
private
MasterElement
(
int
elementId
,
long
elementEndOffset
)
{
this
.
elementId
=
elementId
;
this
.
elementEndOffset
=
elementEndOffset
;
}
}
}
}
library/src/main/java/com/google/android/exoplayer/parser/webm/WebmExtractor.java
View file @
4228f2cf
...
@@ -19,97 +19,22 @@ import com.google.android.exoplayer.MediaFormat;
...
@@ -19,97 +19,22 @@ import com.google.android.exoplayer.MediaFormat;
import
com.google.android.exoplayer.SampleHolder
;
import
com.google.android.exoplayer.SampleHolder
;
import
com.google.android.exoplayer.parser.SegmentIndex
;
import
com.google.android.exoplayer.parser.SegmentIndex
;
import
com.google.android.exoplayer.upstream.NonBlockingInputStream
;
import
com.google.android.exoplayer.upstream.NonBlockingInputStream
;
import
com.google.android.exoplayer.util.LongArray
;
import
com.google.android.exoplayer.util.MimeTypes
;
import
android.annotation.TargetApi
;
import
android.media.MediaExtractor
;
import
java.util.Arrays
;
/**
/**
* Facilitates the extraction of data from the WebM container format with a
* Extractor to facilitate data retrieval from the WebM container format.
* non-blocking, incremental parser based on {@link EbmlReader}.
*
*
* <p>WebM is a subset of the EBML elements defined for Matroska. More information about EBML and
* <p>WebM is a subset of the EBML elements defined for Matroska. More information about EBML and
* Matroska is available <a href="http://www.matroska.org/technical/specs/index.html">here</a>.
* Matroska is available <a href="http://www.matroska.org/technical/specs/index.html">here</a>.
* More info about WebM is <a href="http://www.webmproject.org/code/specs/container/">here</a>.
* More info about WebM is <a href="http://www.webmproject.org/code/specs/container/">here</a>.
*/
*/
@TargetApi
(
16
)
public
interface
WebmExtractor
{
public
final
class
WebmExtractor
extends
EbmlReader
{
private
static
final
String
DOC_TYPE_WEBM
=
"webm"
;
private
static
final
String
CODEC_ID_VP9
=
"V_VP9"
;
private
static
final
int
UNKNOWN
=
-
1
;
// Element IDs
private
static
final
int
ID_EBML
=
0x1A45DFA3
;
private
static
final
int
ID_EBML_READ_VERSION
=
0x42F7
;
private
static
final
int
ID_DOC_TYPE
=
0x4282
;
private
static
final
int
ID_DOC_TYPE_READ_VERSION
=
0x4285
;
private
static
final
int
ID_SEGMENT
=
0x18538067
;
private
static
final
int
ID_INFO
=
0x1549A966
;
private
static
final
int
ID_TIMECODE_SCALE
=
0x2AD7B1
;
private
static
final
int
ID_DURATION
=
0x4489
;
private
static
final
int
ID_CLUSTER
=
0x1F43B675
;
private
static
final
int
ID_TIME_CODE
=
0xE7
;
private
static
final
int
ID_SIMPLE_BLOCK
=
0xA3
;
private
static
final
int
ID_TRACKS
=
0x1654AE6B
;
private
static
final
int
ID_TRACK_ENTRY
=
0xAE
;
private
static
final
int
ID_CODEC_ID
=
0x86
;
private
static
final
int
ID_VIDEO
=
0xE0
;
private
static
final
int
ID_PIXEL_WIDTH
=
0xB0
;
private
static
final
int
ID_PIXEL_HEIGHT
=
0xBA
;
private
static
final
int
ID_CUES
=
0x1C53BB6B
;
private
static
final
int
ID_CUE_POINT
=
0xBB
;
private
static
final
int
ID_CUE_TIME
=
0xB3
;
private
static
final
int
ID_CUE_TRACK_POSITIONS
=
0xB7
;
private
static
final
int
ID_CUE_CLUSTER_POSITION
=
0xF1
;
// SimpleBlock Lacing Values
private
static
final
int
LACING_NONE
=
0
;
private
static
final
int
LACING_XIPH
=
1
;
private
static
final
int
LACING_FIXED
=
2
;
private
static
final
int
LACING_EBML
=
3
;
private
final
byte
[]
simpleBlockTimecodeAndFlags
=
new
byte
[
3
];
private
SampleHolder
tempSampleHolder
;
private
boolean
sampleRead
;
private
boolean
prepared
=
false
;
private
long
segmentStartPosition
=
UNKNOWN
;
private
long
segmentEndPosition
=
UNKNOWN
;
private
long
timecodeScale
=
1000000L
;
private
long
durationUs
=
UNKNOWN
;
private
int
pixelWidth
=
UNKNOWN
;
private
int
pixelHeight
=
UNKNOWN
;
private
int
cuesByteSize
=
UNKNOWN
;
private
long
clusterTimecodeUs
=
UNKNOWN
;
private
long
simpleBlockTimecodeUs
=
UNKNOWN
;
private
MediaFormat
format
;
private
SegmentIndex
cues
;
private
LongArray
cueTimesUs
;
private
LongArray
cueClusterPositions
;
public
WebmExtractor
()
{
cueTimesUs
=
new
LongArray
();
cueClusterPositions
=
new
LongArray
();
}
/**
/**
* Whether the has parsed the cues and sample format from the stream.
* Whether the has parsed the cues and sample format from the stream.
*
*
* @return True if the extractor is prepared. False otherwise
.
* @return True if the extractor is prepared. False otherwise
*/
*/
public
boolean
isPrepared
()
{
public
boolean
isPrepared
();
return
prepared
;
}
/**
/**
* Consumes data from a {@link NonBlockingInputStream}.
* Consumes data from a {@link NonBlockingInputStream}.
...
@@ -118,289 +43,36 @@ public final class WebmExtractor extends EbmlReader {
...
@@ -118,289 +43,36 @@ public final class WebmExtractor extends EbmlReader {
* {@code sampleHolder}. Hence the same {@link SampleHolder} instance must be passed
* {@code sampleHolder}. Hence the same {@link SampleHolder} instance must be passed
* in subsequent calls until the whole sample has been read.
* in subsequent calls until the whole sample has been read.
*
*
* @param inputStream The input stream from which data should be read
.
* @param inputStream The input stream from which data should be read
* @param sampleHolder A {@link SampleHolder} into which the sample should be read
.
* @param sampleHolder A {@link SampleHolder} into which the sample should be read
* @return {@code true} if a sample has been read into the sample holder
, otherwise {@code false}.
* @return {@code true} if a sample has been read into the sample holder
*/
*/
public
boolean
read
(
NonBlockingInputStream
inputStream
,
SampleHolder
sampleHolder
)
{
public
boolean
read
(
NonBlockingInputStream
inputStream
,
SampleHolder
sampleHolder
);
tempSampleHolder
=
sampleHolder
;
sampleRead
=
false
;
super
.
read
(
inputStream
);
tempSampleHolder
=
null
;
return
sampleRead
;
}
/**
/**
* Seeks to a position before or equal to the requested time.
* Seeks to a position before or equal to the requested time.
*
*
* @param seekTimeUs The desired seek time in microseconds
.
* @param seekTimeUs The desired seek time in microseconds
* @param allowNoop Allow the seek operation to do nothing if the seek time is in the current
* @param allowNoop Allow the seek operation to do nothing if the seek time is in the current
* segment, is equal to or greater than the time of the current sample, and if there does not
* segment, is equal to or greater than the time of the current sample, and if there does not
* exist a sync frame between these two times
.
* exist a sync frame between these two times
* @return True if the operation resulted in a change of state. False if it was a no-op
.
* @return True if the operation resulted in a change of state. False if it was a no-op
*/
*/
public
boolean
seekTo
(
long
seekTimeUs
,
boolean
allowNoop
)
{
public
boolean
seekTo
(
long
seekTimeUs
,
boolean
allowNoop
);
checkPrepared
();
if
(
allowNoop
&&
simpleBlockTimecodeUs
!=
UNKNOWN
&&
seekTimeUs
>=
simpleBlockTimecodeUs
)
{
final
int
clusterIndex
=
Arrays
.
binarySearch
(
cues
.
timesUs
,
clusterTimecodeUs
);
if
(
clusterIndex
>=
0
&&
seekTimeUs
<
clusterTimecodeUs
+
cues
.
durationsUs
[
clusterIndex
])
{
return
false
;
}
}
reset
();
return
true
;
}
/**
/**
* Returns the cues for the media stream.
* Returns the cues for the media stream.
*
*
* @return The cues in the form of a {@link SegmentIndex}, or null if the extractor is not yet
* @return The cues in the form of a {@link SegmentIndex}, or null if the extractor is not yet
* prepared
.
* prepared
*/
*/
public
SegmentIndex
getCues
()
{
public
SegmentIndex
getCues
();
checkPrepared
();
return
cues
;
}
/**
/**
* Returns the format of the samples contained within the media stream.
* Returns the format of the samples contained within the media stream.
*
*
* @return The sample media format, or null if the extracted is not yet prepared
.
* @return The sample media format, or null if the extracted is not yet prepared
*/
*/
public
MediaFormat
getFormat
()
{
public
MediaFormat
getFormat
();
checkPrepared
();
return
format
;
}
@Override
protected
int
getElementType
(
int
id
)
{
switch
(
id
)
{
case
ID_EBML:
case
ID_SEGMENT:
case
ID_INFO:
case
ID_CLUSTER:
case
ID_TRACKS:
case
ID_TRACK_ENTRY:
case
ID_VIDEO:
case
ID_CUES:
case
ID_CUE_POINT:
case
ID_CUE_TRACK_POSITIONS:
return
EbmlReader
.
TYPE_MASTER
;
case
ID_EBML_READ_VERSION:
case
ID_DOC_TYPE_READ_VERSION:
case
ID_TIMECODE_SCALE:
case
ID_TIME_CODE:
case
ID_PIXEL_WIDTH:
case
ID_PIXEL_HEIGHT:
case
ID_CUE_TIME:
case
ID_CUE_CLUSTER_POSITION:
return
EbmlReader
.
TYPE_UNSIGNED_INT
;
case
ID_DOC_TYPE:
case
ID_CODEC_ID:
return
EbmlReader
.
TYPE_STRING
;
case
ID_SIMPLE_BLOCK:
return
EbmlReader
.
TYPE_BINARY
;
case
ID_DURATION:
return
EbmlReader
.
TYPE_FLOAT
;
default
:
return
EbmlReader
.
TYPE_UNKNOWN
;
}
}
@Override
protected
boolean
onMasterElementStart
(
int
id
,
long
elementOffset
,
int
headerSize
,
int
contentsSize
)
{
switch
(
id
)
{
case
ID_SEGMENT:
if
(
segmentStartPosition
!=
UNKNOWN
||
segmentEndPosition
!=
UNKNOWN
)
{
throw
new
IllegalStateException
(
"Multiple Segment elements not supported"
);
}
segmentStartPosition
=
elementOffset
+
headerSize
;
segmentEndPosition
=
elementOffset
+
headerSize
+
contentsSize
;
break
;
case
ID_CUES:
cuesByteSize
=
headerSize
+
contentsSize
;
break
;
}
return
true
;
}
@Override
protected
boolean
onMasterElementEnd
(
int
id
)
{
switch
(
id
)
{
case
ID_CUES:
finishPreparing
();
return
false
;
}
return
true
;
}
@Override
protected
boolean
onIntegerElement
(
int
id
,
long
value
)
{
switch
(
id
)
{
case
ID_EBML_READ_VERSION:
// Validate that EBMLReadVersion is supported. This extractor only supports v1.
if
(
value
!=
1
)
{
throw
new
IllegalStateException
(
"EBMLReadVersion "
+
value
+
" not supported"
);
}
break
;
case
ID_DOC_TYPE_READ_VERSION:
// Validate that DocTypeReadVersion is supported. This extractor only supports up to v2.
if
(
value
<
1
||
value
>
2
)
{
throw
new
IllegalStateException
(
"DocTypeReadVersion "
+
value
+
" not supported"
);
}
break
;
case
ID_TIMECODE_SCALE:
timecodeScale
=
value
;
break
;
case
ID_PIXEL_WIDTH:
pixelWidth
=
(
int
)
value
;
break
;
case
ID_PIXEL_HEIGHT:
pixelHeight
=
(
int
)
value
;
break
;
case
ID_CUE_TIME:
cueTimesUs
.
add
(
scaleTimecodeToUs
(
value
));
break
;
case
ID_CUE_CLUSTER_POSITION:
cueClusterPositions
.
add
(
value
);
break
;
case
ID_TIME_CODE:
clusterTimecodeUs
=
scaleTimecodeToUs
(
value
);
break
;
}
return
true
;
}
@Override
protected
boolean
onFloatElement
(
int
id
,
double
value
)
{
switch
(
id
)
{
case
ID_DURATION:
durationUs
=
scaleTimecodeToUs
(
value
);
break
;
}
return
true
;
}
@Override
protected
boolean
onStringElement
(
int
id
,
String
value
)
{
switch
(
id
)
{
case
ID_DOC_TYPE:
// Validate that DocType is supported. This extractor only supports "webm".
if
(!
DOC_TYPE_WEBM
.
equals
(
value
))
{
throw
new
IllegalStateException
(
"DocType "
+
value
+
" not supported"
);
}
break
;
case
ID_CODEC_ID:
// Validate that CodecID is supported. This extractor only supports "V_VP9".
if
(!
CODEC_ID_VP9
.
equals
(
value
))
{
throw
new
IllegalStateException
(
"CodecID "
+
value
+
" not supported"
);
}
break
;
}
return
true
;
}
@Override
protected
boolean
onBinaryElement
(
NonBlockingInputStream
inputStream
,
int
id
,
long
elementOffset
,
int
headerSize
,
int
contentsSize
)
{
switch
(
id
)
{
case
ID_SIMPLE_BLOCK:
// Please refer to http://www.matroska.org/technical/specs/index.html#simpleblock_structure
// for info about how data is organized in a SimpleBlock element.
// Value of trackNumber is not used but needs to be read.
readVarint
(
inputStream
);
// Next three bytes have timecode and flags.
readBytes
(
inputStream
,
simpleBlockTimecodeAndFlags
,
3
);
// First two bytes of the three are the relative timecode.
final
int
timecode
=
(
simpleBlockTimecodeAndFlags
[
0
]
<<
8
)
|
(
simpleBlockTimecodeAndFlags
[
1
]
&
0xff
);
final
long
timecodeUs
=
scaleTimecodeToUs
(
timecode
);
// Last byte of the three has some flags and the lacing value.
final
boolean
keyframe
=
(
simpleBlockTimecodeAndFlags
[
2
]
&
0x80
)
==
0x80
;
final
boolean
invisible
=
(
simpleBlockTimecodeAndFlags
[
2
]
&
0x08
)
==
0x08
;
final
int
lacing
=
(
simpleBlockTimecodeAndFlags
[
2
]
&
0x06
)
>>
1
;
//final boolean discardable = (simpleBlockTimecodeAndFlags[2] & 0x01) == 0x01; // Not used.
// Validate lacing and set info into sample holder.
switch
(
lacing
)
{
case
LACING_NONE:
final
long
elementEndOffset
=
elementOffset
+
headerSize
+
contentsSize
;
simpleBlockTimecodeUs
=
clusterTimecodeUs
+
timecodeUs
;
tempSampleHolder
.
flags
=
keyframe
?
MediaExtractor
.
SAMPLE_FLAG_SYNC
:
0
;
tempSampleHolder
.
decodeOnly
=
invisible
;
tempSampleHolder
.
timeUs
=
clusterTimecodeUs
+
timecodeUs
;
tempSampleHolder
.
size
=
(
int
)
(
elementEndOffset
-
getBytesRead
());
break
;
case
LACING_EBML:
case
LACING_FIXED:
case
LACING_XIPH:
default
:
throw
new
IllegalStateException
(
"Lacing mode "
+
lacing
+
" not supported"
);
}
// Read video data into sample holder.
readBytes
(
inputStream
,
tempSampleHolder
.
data
,
tempSampleHolder
.
size
);
sampleRead
=
true
;
return
false
;
default
:
skipBytes
(
inputStream
,
contentsSize
);
}
return
true
;
}
private
long
scaleTimecodeToUs
(
long
unscaledTimecode
)
{
return
(
unscaledTimecode
*
timecodeScale
)
/
1000L
;
}
private
long
scaleTimecodeToUs
(
double
unscaledTimecode
)
{
return
(
long
)
((
unscaledTimecode
*
timecodeScale
)
/
1000.0
);
}
private
void
checkPrepared
()
{
if
(!
prepared
)
{
throw
new
IllegalStateException
(
"Parser not yet prepared"
);
}
}
private
void
finishPreparing
()
{
if
(
prepared
||
segmentStartPosition
==
UNKNOWN
||
segmentEndPosition
==
UNKNOWN
||
durationUs
==
UNKNOWN
||
pixelWidth
==
UNKNOWN
||
pixelHeight
==
UNKNOWN
||
cuesByteSize
==
UNKNOWN
||
cueTimesUs
.
size
()
==
0
||
cueTimesUs
.
size
()
!=
cueClusterPositions
.
size
())
{
throw
new
IllegalStateException
(
"Incorrect state in finishPreparing()"
);
}
format
=
MediaFormat
.
createVideoFormat
(
MimeTypes
.
VIDEO_VP9
,
MediaFormat
.
NO_VALUE
,
pixelWidth
,
pixelHeight
,
null
);
final
int
cuePointsSize
=
cueTimesUs
.
size
();
final
int
sizeBytes
=
cuesByteSize
;
final
int
[]
sizes
=
new
int
[
cuePointsSize
];
final
long
[]
offsets
=
new
long
[
cuePointsSize
];
final
long
[]
durationsUs
=
new
long
[
cuePointsSize
];
final
long
[]
timesUs
=
new
long
[
cuePointsSize
];
for
(
int
i
=
0
;
i
<
cuePointsSize
;
i
++)
{
timesUs
[
i
]
=
cueTimesUs
.
get
(
i
);
offsets
[
i
]
=
segmentStartPosition
+
cueClusterPositions
.
get
(
i
);
}
for
(
int
i
=
0
;
i
<
cuePointsSize
-
1
;
i
++)
{
sizes
[
i
]
=
(
int
)
(
offsets
[
i
+
1
]
-
offsets
[
i
]);
durationsUs
[
i
]
=
timesUs
[
i
+
1
]
-
timesUs
[
i
];
}
sizes
[
cuePointsSize
-
1
]
=
(
int
)
(
segmentEndPosition
-
offsets
[
cuePointsSize
-
1
]);
durationsUs
[
cuePointsSize
-
1
]
=
durationUs
-
timesUs
[
cuePointsSize
-
1
];
cues
=
new
SegmentIndex
(
sizeBytes
,
sizes
,
offsets
,
durationsUs
,
timesUs
);
cueTimesUs
=
null
;
cueClusterPositions
=
null
;
prepared
=
true
;
}
}
}
library/src/main/java/com/google/android/exoplayer/smoothstreaming/SmoothStreamingChunkSource.java
View file @
4228f2cf
...
@@ -63,7 +63,7 @@ public class SmoothStreamingChunkSource implements ChunkSource {
...
@@ -63,7 +63,7 @@ public class SmoothStreamingChunkSource implements ChunkSource {
private
final
int
maxHeight
;
private
final
int
maxHeight
;
private
final
SparseArray
<
FragmentedMp4Extractor
>
extractors
;
private
final
SparseArray
<
FragmentedMp4Extractor
>
extractors
;
private
final
Format
[]
formats
;
private
final
SmoothStreaming
Format
[]
formats
;
/**
/**
* @param baseUrl The base URL for the streams.
* @param baseUrl The base URL for the streams.
...
@@ -94,23 +94,24 @@ public class SmoothStreamingChunkSource implements ChunkSource {
...
@@ -94,23 +94,24 @@ public class SmoothStreamingChunkSource implements ChunkSource {
}
}
int
trackCount
=
trackIndices
!=
null
?
trackIndices
.
length
:
streamElement
.
tracks
.
length
;
int
trackCount
=
trackIndices
!=
null
?
trackIndices
.
length
:
streamElement
.
tracks
.
length
;
formats
=
new
Format
[
trackCount
];
formats
=
new
SmoothStreaming
Format
[
trackCount
];
extractors
=
new
SparseArray
<
FragmentedMp4Extractor
>();
extractors
=
new
SparseArray
<
FragmentedMp4Extractor
>();
int
maxWidth
=
0
;
int
maxWidth
=
0
;
int
maxHeight
=
0
;
int
maxHeight
=
0
;
for
(
int
i
=
0
;
i
<
trackCount
;
i
++)
{
for
(
int
i
=
0
;
i
<
trackCount
;
i
++)
{
int
trackIndex
=
trackIndices
!=
null
?
trackIndices
[
i
]
:
i
;
int
trackIndex
=
trackIndices
!=
null
?
trackIndices
[
i
]
:
i
;
TrackElement
trackElement
=
streamElement
.
tracks
[
trackIndex
];
TrackElement
trackElement
=
streamElement
.
tracks
[
trackIndex
];
formats
[
i
]
=
new
Format
(
trackIndex
,
trackElement
.
mimeType
,
trackElement
.
maxWidth
,
formats
[
i
]
=
new
SmoothStreamingFormat
(
String
.
valueOf
(
trackIndex
),
trackElement
.
mimeType
,
trackElement
.
max
Height
,
trackElement
.
numChannels
,
trackElement
.
sampleRate
,
trackElement
.
max
Width
,
trackElement
.
maxHeight
,
trackElement
.
numChannels
,
trackElement
.
bitrate
/
8
);
trackElement
.
sampleRate
,
trackElement
.
bitrate
,
trackIndex
);
maxWidth
=
Math
.
max
(
maxWidth
,
trackElement
.
maxWidth
);
maxWidth
=
Math
.
max
(
maxWidth
,
trackElement
.
maxWidth
);
maxHeight
=
Math
.
max
(
maxHeight
,
trackElement
.
maxHeight
);
maxHeight
=
Math
.
max
(
maxHeight
,
trackElement
.
maxHeight
);
MediaFormat
mediaFormat
=
getMediaFormat
(
streamElement
,
trackIndex
);
MediaFormat
mediaFormat
=
getMediaFormat
(
streamElement
,
trackIndex
);
int
trackType
=
streamElement
.
type
==
StreamElement
.
TYPE_VIDEO
?
Track
.
TYPE_VIDEO
int
trackType
=
streamElement
.
type
==
StreamElement
.
TYPE_VIDEO
?
Track
.
TYPE_VIDEO
:
Track
.
TYPE_AUDIO
;
:
Track
.
TYPE_AUDIO
;
FragmentedMp4Extractor
extractor
=
new
FragmentedMp4Extractor
(
true
);
FragmentedMp4Extractor
extractor
=
new
FragmentedMp4Extractor
(
FragmentedMp4Extractor
.
WORKAROUND_EVERY_VIDEO_FRAME_IS_SYNC_FRAME
);
extractor
.
setTrack
(
new
Track
(
trackIndex
,
trackType
,
streamElement
.
timeScale
,
mediaFormat
,
extractor
.
setTrack
(
new
Track
(
trackIndex
,
trackType
,
streamElement
.
timeScale
,
mediaFormat
,
trackEncryptionBoxes
));
trackEncryptionBoxes
));
if
(
protectionElement
!=
null
)
{
if
(
protectionElement
!=
null
)
{
...
@@ -141,7 +142,7 @@ public class SmoothStreamingChunkSource implements ChunkSource {
...
@@ -141,7 +142,7 @@ public class SmoothStreamingChunkSource implements ChunkSource {
}
}
@Override
@Override
public
void
disable
(
List
<
MediaChunk
>
queue
)
{
public
void
disable
(
List
<
?
extends
MediaChunk
>
queue
)
{
// Do nothing.
// Do nothing.
}
}
...
@@ -155,14 +156,14 @@ public class SmoothStreamingChunkSource implements ChunkSource {
...
@@ -155,14 +156,14 @@ public class SmoothStreamingChunkSource implements ChunkSource {
long
playbackPositionUs
,
ChunkOperationHolder
out
)
{
long
playbackPositionUs
,
ChunkOperationHolder
out
)
{
evaluation
.
queueSize
=
queue
.
size
();
evaluation
.
queueSize
=
queue
.
size
();
formatEvaluator
.
evaluate
(
queue
,
playbackPositionUs
,
formats
,
evaluation
);
formatEvaluator
.
evaluate
(
queue
,
playbackPositionUs
,
formats
,
evaluation
);
Format
selectedFormat
=
evaluation
.
format
;
SmoothStreamingFormat
selectedFormat
=
(
SmoothStreamingFormat
)
evaluation
.
format
;
out
.
queueSize
=
evaluation
.
queueSize
;
out
.
queueSize
=
evaluation
.
queueSize
;
if
(
selectedFormat
==
null
)
{
if
(
selectedFormat
==
null
)
{
out
.
chunk
=
null
;
out
.
chunk
=
null
;
return
;
return
;
}
else
if
(
out
.
queueSize
==
queue
.
size
()
&&
out
.
chunk
!=
null
}
else
if
(
out
.
queueSize
==
queue
.
size
()
&&
out
.
chunk
!=
null
&&
out
.
chunk
.
format
.
id
==
evaluation
.
format
.
id
)
{
&&
out
.
chunk
.
format
.
id
.
equals
(
evaluation
.
format
.
id
)
)
{
// We already have a chunk, and the evaluation hasn't changed either the format or the size
// We already have a chunk, and the evaluation hasn't changed either the format or the size
// of the queue. Do nothing.
// of the queue. Do nothing.
return
;
return
;
...
@@ -181,11 +182,12 @@ public class SmoothStreamingChunkSource implements ChunkSource {
...
@@ -181,11 +182,12 @@ public class SmoothStreamingChunkSource implements ChunkSource {
}
}
boolean
isLastChunk
=
nextChunkIndex
==
streamElement
.
chunkCount
-
1
;
boolean
isLastChunk
=
nextChunkIndex
==
streamElement
.
chunkCount
-
1
;
String
requestUrl
=
streamElement
.
buildRequestUrl
(
selectedFormat
.
id
,
nextChunkIndex
);
String
requestUrl
=
streamElement
.
buildRequestUrl
(
selectedFormat
.
trackIndex
,
nextChunkIndex
);
Uri
uri
=
Uri
.
parse
(
baseUrl
+
'/'
+
requestUrl
);
Uri
uri
=
Uri
.
parse
(
baseUrl
+
'/'
+
requestUrl
);
Chunk
mediaChunk
=
newMediaChunk
(
selectedFormat
,
uri
,
null
,
Chunk
mediaChunk
=
newMediaChunk
(
selectedFormat
,
uri
,
null
,
extractors
.
get
(
selectedFormat
.
id
),
dataSource
,
nextChunkIndex
,
isLastChunk
,
extractors
.
get
(
Integer
.
parseInt
(
selectedFormat
.
id
)),
dataSource
,
nextChunkIndex
,
streamElement
.
getStartTimeUs
(
nextChunkIndex
),
isLastChunk
,
streamElement
.
getStartTimeUs
(
nextChunkIndex
),
isLastChunk
?
-
1
:
streamElement
.
getStartTimeUs
(
nextChunkIndex
+
1
),
0
);
isLastChunk
?
-
1
:
streamElement
.
getStartTimeUs
(
nextChunkIndex
+
1
),
0
);
out
.
chunk
=
mediaChunk
;
out
.
chunk
=
mediaChunk
;
}
}
...
@@ -195,6 +197,11 @@ public class SmoothStreamingChunkSource implements ChunkSource {
...
@@ -195,6 +197,11 @@ public class SmoothStreamingChunkSource implements ChunkSource {
return
null
;
return
null
;
}
}
@Override
public
void
onChunkLoadError
(
Chunk
chunk
,
Exception
e
)
{
// Do nothing.
}
private
static
MediaFormat
getMediaFormat
(
StreamElement
streamElement
,
int
trackIndex
)
{
private
static
MediaFormat
getMediaFormat
(
StreamElement
streamElement
,
int
trackIndex
)
{
TrackElement
trackElement
=
streamElement
.
tracks
[
trackIndex
];
TrackElement
trackElement
=
streamElement
.
tracks
[
trackIndex
];
String
mimeType
=
trackElement
.
mimeType
;
String
mimeType
=
trackElement
.
mimeType
;
...
@@ -228,8 +235,8 @@ public class SmoothStreamingChunkSource implements ChunkSource {
...
@@ -228,8 +235,8 @@ public class SmoothStreamingChunkSource implements ChunkSource {
DataSpec
dataSpec
=
new
DataSpec
(
uri
,
offset
,
-
1
,
cacheKey
);
DataSpec
dataSpec
=
new
DataSpec
(
uri
,
offset
,
-
1
,
cacheKey
);
// In SmoothStreaming each chunk contains sample timestamps relative to the start of the chunk.
// In SmoothStreaming each chunk contains sample timestamps relative to the start of the chunk.
// To convert them the absolute timestamps, we need to set sampleOffsetUs to -chunkStartTimeUs.
// To convert them the absolute timestamps, we need to set sampleOffsetUs to -chunkStartTimeUs.
return
new
Mp4MediaChunk
(
dataSource
,
dataSpec
,
formatInfo
,
trigger
,
extractor
,
return
new
Mp4MediaChunk
(
dataSource
,
dataSpec
,
formatInfo
,
trigger
,
chunkStartTimeUs
,
chunkStartTimeUs
,
nextStartTimeUs
,
-
chunkStartTimeUs
,
nextChunkIndex
);
nextStartTimeUs
,
nextChunkIndex
,
extractor
,
false
,
-
chunkStartTimeUs
);
}
}
private
static
byte
[]
getKeyId
(
byte
[]
initData
)
{
private
static
byte
[]
getKeyId
(
byte
[]
initData
)
{
...
@@ -254,4 +261,16 @@ public class SmoothStreamingChunkSource implements ChunkSource {
...
@@ -254,4 +261,16 @@ public class SmoothStreamingChunkSource implements ChunkSource {
data
[
secondPosition
]
=
temp
;
data
[
secondPosition
]
=
temp
;
}
}
private
static
final
class
SmoothStreamingFormat
extends
Format
{
public
final
int
trackIndex
;
public
SmoothStreamingFormat
(
String
id
,
String
mimeType
,
int
width
,
int
height
,
int
numChannels
,
int
audioSamplingRate
,
int
bitrate
,
int
trackIndex
)
{
super
(
id
,
mimeType
,
width
,
height
,
numChannels
,
audioSamplingRate
,
bitrate
);
this
.
trackIndex
=
trackIndex
;
}
}
}
}
library/src/main/java/com/google/android/exoplayer/smoothstreaming/SmoothStreamingManifest.java
View file @
4228f2cf
...
@@ -16,8 +16,8 @@
...
@@ -16,8 +16,8 @@
package
com
.
google
.
android
.
exoplayer
.
smoothstreaming
;
package
com
.
google
.
android
.
exoplayer
.
smoothstreaming
;
import
com.google.android.exoplayer.util.MimeTypes
;
import
com.google.android.exoplayer.util.MimeTypes
;
import
com.google.android.exoplayer.util.Util
;
import
java.util.Arrays
;
import
java.util.UUID
;
import
java.util.UUID
;
/**
/**
...
@@ -195,9 +195,7 @@ public class SmoothStreamingManifest {
...
@@ -195,9 +195,7 @@ public class SmoothStreamingManifest {
* @return The index of the corresponding chunk.
* @return The index of the corresponding chunk.
*/
*/
public
int
getChunkIndex
(
long
timeUs
)
{
public
int
getChunkIndex
(
long
timeUs
)
{
long
time
=
(
timeUs
*
timeScale
)
/
1000000L
;
return
Util
.
binarySearchFloor
(
chunkStartTimes
,
(
timeUs
*
timeScale
)
/
1000000L
,
true
,
true
);
int
chunkIndex
=
Arrays
.
binarySearch
(
chunkStartTimes
,
time
);
return
chunkIndex
<
0
?
-
chunkIndex
-
2
:
chunkIndex
;
}
}
/**
/**
...
...
library/src/main/java/com/google/android/exoplayer/smoothstreaming/SmoothStreamingManifestFetcher.java
View file @
4228f2cf
...
@@ -18,6 +18,8 @@ package com.google.android.exoplayer.smoothstreaming;
...
@@ -18,6 +18,8 @@ package com.google.android.exoplayer.smoothstreaming;
import
com.google.android.exoplayer.ParserException
;
import
com.google.android.exoplayer.ParserException
;
import
com.google.android.exoplayer.util.ManifestFetcher
;
import
com.google.android.exoplayer.util.ManifestFetcher
;
import
android.net.Uri
;
import
org.xmlpull.v1.XmlPullParserException
;
import
org.xmlpull.v1.XmlPullParserException
;
import
java.io.IOException
;
import
java.io.IOException
;
...
@@ -56,7 +58,7 @@ public final class SmoothStreamingManifestFetcher extends ManifestFetcher<Smooth
...
@@ -56,7 +58,7 @@ public final class SmoothStreamingManifestFetcher extends ManifestFetcher<Smooth
@Override
@Override
protected
SmoothStreamingManifest
parse
(
InputStream
stream
,
String
inputEncoding
,
protected
SmoothStreamingManifest
parse
(
InputStream
stream
,
String
inputEncoding
,
String
contentId
)
throws
IOException
,
ParserException
{
String
contentId
,
Uri
baseUrl
)
throws
IOException
,
ParserException
{
try
{
try
{
return
parser
.
parse
(
stream
,
inputEncoding
);
return
parser
.
parse
(
stream
,
inputEncoding
);
}
catch
(
XmlPullParserException
e
)
{
}
catch
(
XmlPullParserException
e
)
{
...
...
library/src/main/java/com/google/android/exoplayer/text/ttml/TtmlSubtitle.java
View file @
4228f2cf
...
@@ -16,8 +16,7 @@
...
@@ -16,8 +16,7 @@
package
com
.
google
.
android
.
exoplayer
.
text
.
ttml
;
package
com
.
google
.
android
.
exoplayer
.
text
.
ttml
;
import
com.google.android.exoplayer.text.Subtitle
;
import
com.google.android.exoplayer.text.Subtitle
;
import
com.google.android.exoplayer.util.Util
;
import
java.util.Arrays
;
/**
/**
* A representation of a TTML subtitle.
* A representation of a TTML subtitle.
...
@@ -41,8 +40,7 @@ public final class TtmlSubtitle implements Subtitle {
...
@@ -41,8 +40,7 @@ public final class TtmlSubtitle implements Subtitle {
@Override
@Override
public
int
getNextEventTimeIndex
(
long
timeUs
)
{
public
int
getNextEventTimeIndex
(
long
timeUs
)
{
int
index
=
Arrays
.
binarySearch
(
eventTimesUs
,
timeUs
-
startTimeUs
);
int
index
=
Util
.
binarySearchCeil
(
eventTimesUs
,
timeUs
-
startTimeUs
,
false
,
false
);
index
=
index
>=
0
?
index
+
1
:
~
index
;
return
index
<
eventTimesUs
.
length
?
index
:
-
1
;
return
index
<
eventTimesUs
.
length
?
index
:
-
1
;
}
}
...
...
library/src/main/java/com/google/android/exoplayer/upstream/DataSourceStream.java
View file @
4228f2cf
...
@@ -176,7 +176,7 @@ public final class DataSourceStream implements Loadable, NonBlockingInputStream
...
@@ -176,7 +176,7 @@ public final class DataSourceStream implements Loadable, NonBlockingInputStream
*/
*/
private
int
read
(
ByteBuffer
target
,
byte
[]
targetArray
,
int
targetArrayOffset
,
private
int
read
(
ByteBuffer
target
,
byte
[]
targetArray
,
int
targetArrayOffset
,
ReadHead
readHead
,
int
readLength
)
{
ReadHead
readHead
,
int
readLength
)
{
if
(
readHead
.
position
==
dataSpec
.
length
)
{
if
(
isEndOfStream
()
)
{
return
-
1
;
return
-
1
;
}
}
int
bytesToRead
=
(
int
)
Math
.
min
(
loadPosition
-
readHead
.
position
,
readLength
);
int
bytesToRead
=
(
int
)
Math
.
min
(
loadPosition
-
readHead
.
position
,
readLength
);
...
...
library/src/main/java/com/google/android/exoplayer/upstream/DefaultBandwidthMeter.java
View file @
4228f2cf
...
@@ -115,8 +115,8 @@ public class DefaultBandwidthMeter implements BandwidthMeter, TransferListener {
...
@@ -115,8 +115,8 @@ public class DefaultBandwidthMeter implements BandwidthMeter, TransferListener {
float
bytesPerSecond
=
accumulator
*
1000
/
elapsedMs
;
float
bytesPerSecond
=
accumulator
*
1000
/
elapsedMs
;
slidingPercentile
.
addSample
(
computeWeight
(
accumulator
),
bytesPerSecond
);
slidingPercentile
.
addSample
(
computeWeight
(
accumulator
),
bytesPerSecond
);
float
bandwidthEstimateFloat
=
slidingPercentile
.
getPercentile
(
0.5f
);
float
bandwidthEstimateFloat
=
slidingPercentile
.
getPercentile
(
0.5f
);
bandwidthEstimate
=
bandwidthEstimateFloat
==
Float
.
NaN
bandwidthEstimate
=
Float
.
isNaN
(
bandwidthEstimateFloat
)
?
NO_ESTIMATE
?
NO_ESTIMATE
:
(
long
)
bandwidthEstimateFloat
;
:
(
long
)
bandwidthEstimateFloat
;
notifyBandwidthSample
(
elapsedMs
,
accumulator
,
bandwidthEstimate
);
notifyBandwidthSample
(
elapsedMs
,
accumulator
,
bandwidthEstimate
);
}
}
streamCount
--;
streamCount
--;
...
...
library/src/main/java/com/google/android/exoplayer/upstream/cache/Cache.java
View file @
4228f2cf
...
@@ -134,9 +134,8 @@ public interface Cache {
...
@@ -134,9 +134,8 @@ public interface Cache {
* @param key The key of the data being requested.
* @param key The key of the data being requested.
* @param position The position of the data being requested.
* @param position The position of the data being requested.
* @return The {@link CacheSpan}. Or null if the cache entry is locked.
* @return The {@link CacheSpan}. Or null if the cache entry is locked.
* @throws InterruptedException
*/
*/
CacheSpan
startReadWriteNonBlocking
(
String
key
,
long
position
)
throws
InterruptedException
;
CacheSpan
startReadWriteNonBlocking
(
String
key
,
long
position
);
/**
/**
* Obtains a cache file into which data can be written. Must only be called when holding a
* Obtains a cache file into which data can be written. Must only be called when holding a
...
@@ -173,4 +172,14 @@ public interface Cache {
...
@@ -173,4 +172,14 @@ public interface Cache {
*/
*/
void
removeSpan
(
CacheSpan
span
);
void
removeSpan
(
CacheSpan
span
);
/**
* Queries if a range is entirely available in the cache.
*
* @param key The cache key for the data.
* @param position The starting position of the data.
* @param length The length of the data.
* @return true if the data is available in the Cache otherwise false;
*/
boolean
isCached
(
String
key
,
long
position
,
long
length
);
}
}
library/src/main/java/com/google/android/exoplayer/upstream/cache/SimpleCache.java
View file @
4228f2cf
...
@@ -109,26 +109,29 @@ public class SimpleCache implements Cache {
...
@@ -109,26 +109,29 @@ public class SimpleCache implements Cache {
public
synchronized
CacheSpan
startReadWrite
(
String
key
,
long
position
)
public
synchronized
CacheSpan
startReadWrite
(
String
key
,
long
position
)
throws
InterruptedException
{
throws
InterruptedException
{
CacheSpan
lookupSpan
=
CacheSpan
.
createLookup
(
key
,
position
);
CacheSpan
lookupSpan
=
CacheSpan
.
createLookup
(
key
,
position
);
// Wait until no-one holds a lock for the key.
while
(
true
)
{
while
(
lockedSpans
.
containsKey
(
key
))
{
CacheSpan
span
=
startReadWriteNonBlocking
(
lookupSpan
);
if
(
span
!=
null
)
{
return
span
;
}
else
{
// Write case, lock not available. We'll be woken up when a locked span is released (if the
// released lock is for the requested key then we'll be able to make progress) or when a
// span is added to the cache (if the span is for the requested key and covers the requested
// position, then we'll become a read and be able to make progress).
wait
();
wait
();
}
}
return
getSpanningRegion
(
key
,
lookupSpan
);
}
}
}
@Override
@Override
public
synchronized
CacheSpan
startReadWriteNonBlocking
(
String
key
,
long
position
)
public
synchronized
CacheSpan
startReadWriteNonBlocking
(
String
key
,
long
position
)
{
throws
InterruptedException
{
return
startReadWriteNonBlocking
(
CacheSpan
.
createLookup
(
key
,
position
));
CacheSpan
lookupSpan
=
CacheSpan
.
createLookup
(
key
,
position
);
// Return null if key is locked
if
(
lockedSpans
.
containsKey
(
key
))
{
return
null
;
}
return
getSpanningRegion
(
key
,
lookupSpan
);
}
}
private
CacheSpan
getSpanningRegion
(
String
key
,
CacheSpan
lookupSpan
)
{
private
synchronized
CacheSpan
startReadWriteNonBlocking
(
CacheSpan
lookupSpan
)
{
CacheSpan
spanningRegion
=
getSpan
(
lookupSpan
);
CacheSpan
spanningRegion
=
getSpan
(
lookupSpan
);
// Read case.
if
(
spanningRegion
.
isCached
)
{
if
(
spanningRegion
.
isCached
)
{
CacheSpan
oldCacheSpan
=
spanningRegion
;
CacheSpan
oldCacheSpan
=
spanningRegion
;
// Remove the old span from the in-memory representation.
// Remove the old span from the in-memory representation.
...
@@ -139,12 +142,19 @@ public class SimpleCache implements Cache {
...
@@ -139,12 +142,19 @@ public class SimpleCache implements Cache {
// Add the updated span back into the in-memory representation.
// Add the updated span back into the in-memory representation.
spansForKey
.
add
(
spanningRegion
);
spansForKey
.
add
(
spanningRegion
);
notifySpanTouched
(
oldCacheSpan
,
spanningRegion
);
notifySpanTouched
(
oldCacheSpan
,
spanningRegion
);
}
else
{
return
spanningRegion
;
lockedSpans
.
put
(
key
,
spanningRegion
);
}
}
// Write case, lock available.
if
(!
lockedSpans
.
containsKey
(
lookupSpan
.
key
))
{
lockedSpans
.
put
(
lookupSpan
.
key
,
spanningRegion
);
return
spanningRegion
;
return
spanningRegion
;
}
}
// Write case, lock not available.
return
null
;
}
@Override
@Override
public
synchronized
File
startFile
(
String
key
,
long
position
,
long
length
)
{
public
synchronized
File
startFile
(
String
key
,
long
position
,
long
length
)
{
Assertions
.
checkState
(
lockedSpans
.
containsKey
(
key
));
Assertions
.
checkState
(
lockedSpans
.
containsKey
(
key
));
...
@@ -173,6 +183,7 @@ public class SimpleCache implements Cache {
...
@@ -173,6 +183,7 @@ public class SimpleCache implements Cache {
return
;
return
;
}
}
addSpan
(
span
);
addSpan
(
span
);
notifyAll
();
}
}
@Override
@Override
...
@@ -330,4 +341,41 @@ public class SimpleCache implements Cache {
...
@@ -330,4 +341,41 @@ public class SimpleCache implements Cache {
evictor
.
onSpanTouched
(
this
,
oldSpan
,
newSpan
);
evictor
.
onSpanTouched
(
this
,
oldSpan
,
newSpan
);
}
}
@Override
public
synchronized
boolean
isCached
(
String
key
,
long
position
,
long
length
)
{
TreeSet
<
CacheSpan
>
entries
=
cachedSpans
.
get
(
key
);
if
(
entries
==
null
)
{
return
false
;
}
CacheSpan
lookupSpan
=
CacheSpan
.
createLookup
(
key
,
position
);
CacheSpan
floorSpan
=
entries
.
floor
(
lookupSpan
);
if
(
floorSpan
==
null
||
floorSpan
.
position
+
floorSpan
.
length
<=
position
)
{
// We don't have a span covering the start of the queried region.
return
false
;
}
long
queryEndPosition
=
position
+
length
;
long
currentEndPosition
=
floorSpan
.
position
+
floorSpan
.
length
;
if
(
currentEndPosition
>=
queryEndPosition
)
{
// floorSpan covers the queried region.
return
true
;
}
Iterator
<
CacheSpan
>
iterator
=
entries
.
tailSet
(
floorSpan
,
false
).
iterator
();
while
(
iterator
.
hasNext
())
{
CacheSpan
next
=
iterator
.
next
();
if
(
next
.
position
>
currentEndPosition
)
{
// There's a hole in the cache within the queried region.
return
false
;
}
// We expect currentEndPosition to always equal (next.position + next.length), but
// perform a max check anyway to guard against the existence of overlapping spans.
currentEndPosition
=
Math
.
max
(
currentEndPosition
,
next
.
position
+
next
.
length
);
if
(
currentEndPosition
>=
queryEndPosition
)
{
// We've found spans covering the queried region.
return
true
;
}
}
// We ran out of spans before covering the queried region.
return
false
;
}
}
}
library/src/main/java/com/google/android/exoplayer/util/ManifestFetcher.java
View file @
4228f2cf
...
@@ -17,6 +17,7 @@ package com.google.android.exoplayer.util;
...
@@ -17,6 +17,7 @@ package com.google.android.exoplayer.util;
import
com.google.android.exoplayer.ParserException
;
import
com.google.android.exoplayer.ParserException
;
import
android.net.Uri
;
import
android.os.AsyncTask
;
import
android.os.AsyncTask
;
import
java.io.IOException
;
import
java.io.IOException
;
...
@@ -84,14 +85,15 @@ public abstract class ManifestFetcher<T> extends AsyncTask<String, Void, T> {
...
@@ -84,14 +85,15 @@ public abstract class ManifestFetcher<T> extends AsyncTask<String, Void, T> {
protected
final
T
doInBackground
(
String
...
data
)
{
protected
final
T
doInBackground
(
String
...
data
)
{
try
{
try
{
contentId
=
data
.
length
>
1
?
data
[
1
]
:
null
;
contentId
=
data
.
length
>
1
?
data
[
1
]
:
null
;
URL
url
=
new
URL
(
data
[
0
])
;
String
urlString
=
data
[
0
]
;
String
inputEncoding
=
null
;
String
inputEncoding
=
null
;
InputStream
inputStream
=
null
;
InputStream
inputStream
=
null
;
try
{
try
{
HttpURLConnection
connection
=
configureHttpConnection
(
url
);
Uri
baseUrl
=
Util
.
parseBaseUri
(
urlString
);
HttpURLConnection
connection
=
configureHttpConnection
(
new
URL
(
urlString
));
inputStream
=
connection
.
getInputStream
();
inputStream
=
connection
.
getInputStream
();
inputEncoding
=
connection
.
getContentEncoding
();
inputEncoding
=
connection
.
getContentEncoding
();
return
parse
(
inputStream
,
inputEncoding
,
contentId
);
return
parse
(
inputStream
,
inputEncoding
,
contentId
,
baseUrl
);
}
finally
{
}
finally
{
if
(
inputStream
!=
null
)
{
if
(
inputStream
!=
null
)
{
inputStream
.
close
();
inputStream
.
close
();
...
@@ -119,11 +121,13 @@ public abstract class ManifestFetcher<T> extends AsyncTask<String, Void, T> {
...
@@ -119,11 +121,13 @@ public abstract class ManifestFetcher<T> extends AsyncTask<String, Void, T> {
* @param stream The input stream to read.
* @param stream The input stream to read.
* @param inputEncoding The encoding of the input stream.
* @param inputEncoding The encoding of the input stream.
* @param contentId The content id of the media.
* @param contentId The content id of the media.
* @param baseUrl Required where the manifest contains urls that are relative to a base url. May
* be null where this is not the case.
* @throws IOException If an error occurred loading the data.
* @throws IOException If an error occurred loading the data.
* @throws ParserException If an error occurred parsing the loaded data.
* @throws ParserException If an error occurred parsing the loaded data.
*/
*/
protected
abstract
T
parse
(
InputStream
stream
,
String
inputEncoding
,
String
contentId
)
throws
protected
abstract
T
parse
(
InputStream
stream
,
String
inputEncoding
,
String
contentId
,
IOException
,
ParserException
;
Uri
baseUrl
)
throws
IOException
,
ParserException
;
private
HttpURLConnection
configureHttpConnection
(
URL
url
)
throws
IOException
{
private
HttpURLConnection
configureHttpConnection
(
URL
url
)
throws
IOException
{
HttpURLConnection
connection
=
(
HttpURLConnection
)
url
.
openConnection
();
HttpURLConnection
connection
=
(
HttpURLConnection
)
url
.
openConnection
();
...
...
library/src/main/java/com/google/android/exoplayer/util/MimeTypes.java
View file @
4228f2cf
...
@@ -20,25 +20,47 @@ package com.google.android.exoplayer.util;
...
@@ -20,25 +20,47 @@ package com.google.android.exoplayer.util;
*/
*/
public
class
MimeTypes
{
public
class
MimeTypes
{
public
static
final
String
VIDEO_MP4
=
"video/mp4"
;
public
static
final
String
BASE_TYPE_VIDEO
=
"video"
;
public
static
final
String
VIDEO_WEBM
=
"video/webm"
;
public
static
final
String
BASE_TYPE_AUDIO
=
"audio"
;
public
static
final
String
VIDEO_H264
=
"video/avc"
;
public
static
final
String
BASE_TYPE_TEXT
=
"text"
;
public
static
final
String
VIDEO_VP9
=
"video/x-vnd.on2.vp9"
;
public
static
final
String
BASE_TYPE_APPLICATION
=
"application"
;
public
static
final
String
AUDIO_MP4
=
"audio/mp4"
;
public
static
final
String
AUDIO_AAC
=
"audio/mp4a-latm"
;
public
static
final
String
VIDEO_MP4
=
BASE_TYPE_VIDEO
+
"/mp4"
;
public
static
final
String
TEXT_VTT
=
"text/vtt"
;
public
static
final
String
VIDEO_WEBM
=
BASE_TYPE_VIDEO
+
"/webm"
;
public
static
final
String
APPLICATION_TTML
=
"application/ttml+xml"
;
public
static
final
String
VIDEO_H264
=
BASE_TYPE_VIDEO
+
"/avc"
;
public
static
final
String
VIDEO_VP9
=
BASE_TYPE_VIDEO
+
"/x-vnd.on2.vp9"
;
public
static
final
String
AUDIO_MP4
=
BASE_TYPE_AUDIO
+
"/mp4"
;
public
static
final
String
AUDIO_AAC
=
BASE_TYPE_AUDIO
+
"/mp4a-latm"
;
public
static
final
String
TEXT_VTT
=
BASE_TYPE_TEXT
+
"/vtt"
;
public
static
final
String
APPLICATION_TTML
=
BASE_TYPE_APPLICATION
+
"/ttml+xml"
;
private
MimeTypes
()
{}
private
MimeTypes
()
{}
/**
/**
* Returns the top-level type of {@code mimeType}.
*
* @param mimeType The mimeType whose top-level type is required.
* @return The top-level type.
*/
public
static
String
getTopLevelType
(
String
mimeType
)
{
int
indexOfSlash
=
mimeType
.
indexOf
(
'/'
);
if
(
indexOfSlash
==
-
1
)
{
throw
new
IllegalArgumentException
(
"Invalid mime type: "
+
mimeType
);
}
return
mimeType
.
substring
(
0
,
indexOfSlash
);
}
/**
* Whether the top-level type of {@code mimeType} is audio.
* Whether the top-level type of {@code mimeType} is audio.
*
*
* @param mimeType The mimeType to test.
* @param mimeType The mimeType to test.
* @return Whether the top level type is audio.
* @return Whether the top level type is audio.
*/
*/
public
static
boolean
isAudio
(
String
mimeType
)
{
public
static
boolean
isAudio
(
String
mimeType
)
{
return
mimeType
.
startsWith
(
"audio/"
);
return
getTopLevelType
(
mimeType
).
equals
(
BASE_TYPE_AUDIO
);
}
}
/**
/**
...
@@ -48,7 +70,7 @@ public class MimeTypes {
...
@@ -48,7 +70,7 @@ public class MimeTypes {
* @return Whether the top level type is video.
* @return Whether the top level type is video.
*/
*/
public
static
boolean
isVideo
(
String
mimeType
)
{
public
static
boolean
isVideo
(
String
mimeType
)
{
return
mimeType
.
startsWith
(
"video/"
);
return
getTopLevelType
(
mimeType
).
equals
(
BASE_TYPE_VIDEO
);
}
}
/**
/**
...
@@ -58,7 +80,27 @@ public class MimeTypes {
...
@@ -58,7 +80,27 @@ public class MimeTypes {
* @return Whether the top level type is text.
* @return Whether the top level type is text.
*/
*/
public
static
boolean
isText
(
String
mimeType
)
{
public
static
boolean
isText
(
String
mimeType
)
{
return
mimeType
.
startsWith
(
"text/"
);
return
getTopLevelType
(
mimeType
).
equals
(
BASE_TYPE_TEXT
);
}
/**
* Whether the top-level type of {@code mimeType} is application.
*
* @param mimeType The mimeType to test.
* @return Whether the top level type is application.
*/
public
static
boolean
isApplication
(
String
mimeType
)
{
return
getTopLevelType
(
mimeType
).
equals
(
BASE_TYPE_APPLICATION
);
}
/**
* Whether the mimeType is {@link #APPLICATION_TTML}.
*
* @param mimeType The mimeType to test.
* @return Whether the mimeType is {@link #APPLICATION_TTML}.
*/
public
static
boolean
isTtml
(
String
mimeType
)
{
return
mimeType
.
equals
(
APPLICATION_TTML
);
}
}
}
}
library/src/main/java/com/google/android/exoplayer/util/Util.java
View file @
4228f2cf
...
@@ -17,8 +17,13 @@ package com.google.android.exoplayer.util;
...
@@ -17,8 +17,13 @@ package com.google.android.exoplayer.util;
import
com.google.android.exoplayer.upstream.DataSource
;
import
com.google.android.exoplayer.upstream.DataSource
;
import
android.net.Uri
;
import
java.io.IOException
;
import
java.io.IOException
;
import
java.net.URL
;
import
java.net.URL
;
import
java.util.Arrays
;
import
java.util.Collections
;
import
java.util.List
;
import
java.util.Locale
;
import
java.util.Locale
;
import
java.util.concurrent.ExecutorService
;
import
java.util.concurrent.ExecutorService
;
import
java.util.concurrent.Executors
;
import
java.util.concurrent.Executors
;
...
@@ -112,4 +117,99 @@ public final class Util {
...
@@ -112,4 +117,99 @@ public final class Util {
return
text
==
null
?
null
:
text
.
toLowerCase
(
Locale
.
US
);
return
text
==
null
?
null
:
text
.
toLowerCase
(
Locale
.
US
);
}
}
/**
* Like {@link Uri#parse(String)}, but discards the part of the uri that follows the final
* forward slash.
*
* @param uriString An RFC 2396-compliant, encoded uri.
* @return The parsed base uri.
*/
public
static
Uri
parseBaseUri
(
String
uriString
)
{
return
Uri
.
parse
(
uriString
.
substring
(
0
,
uriString
.
lastIndexOf
(
'/'
)));
}
/**
* Returns the index of the largest value in an array that is less than (or optionally equal to)
* a specified key.
* <p>
* The search is performed using a binary search algorithm, and so the array must be sorted.
*
* @param a The array to search.
* @param key The key being searched for.
* @param inclusive If the key is present in the array, whether to return the corresponding index.
* If false then the returned index corresponds to the largest value in the array that is
* strictly less than the key.
* @param stayInBounds If true, then 0 will be returned in the case that the key is smaller than
* the smallest value in the array. If false then -1 will be returned.
*/
public
static
int
binarySearchFloor
(
long
[]
a
,
long
key
,
boolean
inclusive
,
boolean
stayInBounds
)
{
int
index
=
Arrays
.
binarySearch
(
a
,
key
);
index
=
index
<
0
?
-(
index
+
2
)
:
(
inclusive
?
index
:
(
index
-
1
));
return
stayInBounds
?
Math
.
max
(
0
,
index
)
:
index
;
}
/**
* Returns the index of the smallest value in an array that is greater than (or optionally equal
* to) a specified key.
* <p>
* The search is performed using a binary search algorithm, and so the array must be sorted.
*
* @param a The array to search.
* @param key The key being searched for.
* @param inclusive If the key is present in the array, whether to return the corresponding index.
* If false then the returned index corresponds to the smallest value in the array that is
* strictly greater than the key.
* @param stayInBounds If true, then {@code (a.length - 1)} will be returned in the case that the
* key is greater than the largest value in the array. If false then {@code a.length} will be
* returned.
*/
public
static
int
binarySearchCeil
(
long
[]
a
,
long
key
,
boolean
inclusive
,
boolean
stayInBounds
)
{
int
index
=
Arrays
.
binarySearch
(
a
,
key
);
index
=
index
<
0
?
~
index
:
(
inclusive
?
index
:
(
index
+
1
));
return
stayInBounds
?
Math
.
min
(
a
.
length
-
1
,
index
)
:
index
;
}
/**
* Returns the index of the largest value in an list that is less than (or optionally equal to)
* a specified key.
* <p>
* The search is performed using a binary search algorithm, and so the list must be sorted.
*
* @param list The list to search.
* @param key The key being searched for.
* @param inclusive If the key is present in the list, whether to return the corresponding index.
* If false then the returned index corresponds to the largest value in the list that is
* strictly less than the key.
* @param stayInBounds If true, then 0 will be returned in the case that the key is smaller than
* the smallest value in the list. If false then -1 will be returned.
*/
public
static
<
T
>
int
binarySearchFloor
(
List
<?
extends
Comparable
<?
super
T
>>
list
,
T
key
,
boolean
inclusive
,
boolean
stayInBounds
)
{
int
index
=
Collections
.
binarySearch
(
list
,
key
);
index
=
index
<
0
?
-(
index
+
2
)
:
(
inclusive
?
index
:
(
index
-
1
));
return
stayInBounds
?
Math
.
max
(
0
,
index
)
:
index
;
}
/**
* Returns the index of the smallest value in an list that is greater than (or optionally equal
* to) a specified key.
* <p>
* The search is performed using a binary search algorithm, and so the list must be sorted.
*
* @param list The list to search.
* @param key The key being searched for.
* @param inclusive If the key is present in the list, whether to return the corresponding index.
* If false then the returned index corresponds to the smallest value in the list that is
* strictly greater than the key.
* @param stayInBounds If true, then {@code (list.size() - 1)} will be returned in the case that
* the key is greater than the largest value in the list. If false then {@code list.size()}
* will be returned.
*/
public
static
<
T
>
int
binarySearchCeil
(
List
<?
extends
Comparable
<?
super
T
>>
list
,
T
key
,
boolean
inclusive
,
boolean
stayInBounds
)
{
int
index
=
Collections
.
binarySearch
(
list
,
key
);
index
=
index
<
0
?
~
index
:
(
inclusive
?
index
:
(
index
+
1
));
return
stayInBounds
?
Math
.
min
(
list
.
size
()
-
1
,
index
)
:
index
;
}
}
}
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