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
79c2f535
authored
Aug 14, 2014
by
ojw28
Browse files
Options
_('Browse Files')
Download
Plain Diff
Merge pull request #29 from google/dev
Merge 1.0.12 to master
parents
553a1d2e
cc04fd1e
Hide whitespace changes
Inline
Side-by-side
Showing
57 changed files
with
1085 additions
and
732 deletions
.gitignore
README.md
build.gradle
demo/src/main/AndroidManifest.xml
demo/src/main/java/com/google/android/exoplayer/demo/full/EventLogger.java
demo/src/main/java/com/google/android/exoplayer/demo/full/player/DebugTrackRenderer.java
demo/src/main/java/com/google/android/exoplayer/demo/full/player/DemoPlayer.java
library/build.gradle
library/src/main/java/com/google/android/exoplayer/C.java
library/src/main/java/com/google/android/exoplayer/CodecCounters.java
library/src/main/java/com/google/android/exoplayer/ExoPlayer.java
library/src/main/java/com/google/android/exoplayer/ExoPlayerImplInternal.java
library/src/main/java/com/google/android/exoplayer/ExoPlayerLibraryInfo.java
library/src/main/java/com/google/android/exoplayer/FrameworkSampleSource.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/MediaFormat.java
library/src/main/java/com/google/android/exoplayer/SampleSource.java
library/src/main/java/com/google/android/exoplayer/TrackRenderer.java
library/src/main/java/com/google/android/exoplayer/chunk/Chunk.java
library/src/main/java/com/google/android/exoplayer/chunk/ChunkSampleSource.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/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/MediaPresentationDescriptionParser.java
library/src/main/java/com/google/android/exoplayer/parser/mp4/Atom.java
library/src/main/java/com/google/android/exoplayer/parser/mp4/CodecSpecificDataUtil.java
library/src/main/java/com/google/android/exoplayer/parser/mp4/FragmentedMp4Extractor.java
library/src/main/java/com/google/android/exoplayer/parser/mp4/ParsableByteArray.java
library/src/main/java/com/google/android/exoplayer/parser/mp4/TrackEncryptionBox.java
library/src/main/java/com/google/android/exoplayer/parser/mp4/TrackFragment.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/text/TextTrackRenderer.java
library/src/main/java/com/google/android/exoplayer/upstream/Allocation.java
library/src/main/java/com/google/android/exoplayer/upstream/BandwidthMeter.java
library/src/main/java/com/google/android/exoplayer/upstream/BufferPool.java
library/src/main/java/com/google/android/exoplayer/upstream/ByteArrayDataSink.java
library/src/main/java/com/google/android/exoplayer/upstream/ByteArrayDataSource.java
library/src/main/java/com/google/android/exoplayer/upstream/DataSource.java
library/src/main/java/com/google/android/exoplayer/upstream/DataSourceStream.java
library/src/main/java/com/google/android/exoplayer/upstream/DataSpec.java
library/src/main/java/com/google/android/exoplayer/upstream/DefaultBandwidthMeter.java
library/src/main/java/com/google/android/exoplayer/upstream/FileDataSource.java
library/src/main/java/com/google/android/exoplayer/upstream/HttpDataSource.java
library/src/main/java/com/google/android/exoplayer/upstream/TeeDataSource.java
library/src/main/java/com/google/android/exoplayer/upstream/cache/CacheDataSink.java
library/src/main/java/com/google/android/exoplayer/upstream/cache/CacheDataSource.java
.gitignore
0 → 100644
View file @
79c2f535
# Android generated
bin
gen
lint.xml
# IntelliJ IDEA
.idea
*.iml
*.ipr
*.iws
classes
gen-external-apklibs
# Eclipse
.project
.classpath
.settings
.checkstyle
# Gradle
.gradle
build
out
# Maven
target
release.properties
pom.xml.*
# Ant
ant.properties
local.properties
proguard.cfg
proguard-project.txt
# Other
.DS_Store
dist
tmp
README.md
View file @
79c2f535
...
...
@@ -55,6 +55,22 @@ accompanying demo application. To get started:
## Using Gradle ##
ExoPlayer can also be built using Gradle.
For a complete list of tasks, run:
ExoPlayer can also be built using Gradle.
You can include it as a dependent project and build from source. e.g.
./gradlew tasks
```
// setting.gradle
include ':app', ':..:ExoPlayer:library'
// app/build.gradle
dependencies {
compile project(':..:ExoPlayer:library')
}
```
If you want to use ExoPlayer as a jar, run:
```
./gradlew jarRelease
```
and copy library.jar to the libs-folder of your new project.
build.gradle
View file @
79c2f535
...
...
@@ -19,7 +19,7 @@ buildscript {
mavenCentral
()
}
dependencies
{
classpath
'com.android.tools.build:gradle:0.1
0
.+'
classpath
'com.android.tools.build:gradle:0.1
2
.+'
}
}
...
...
demo/src/main/AndroidManifest.xml
View file @
79c2f535
...
...
@@ -16,8 +16,8 @@
<manifest
xmlns:android=
"http://schemas.android.com/apk/res/android"
package=
"com.google.android.exoplayer.demo"
android:versionCode=
"101
0
"
android:versionName=
"1.0.1
0
"
android:versionCode=
"101
2
"
android:versionName=
"1.0.1
2
"
android:theme=
"@style/RootTheme"
>
<uses-permission
android:name=
"android.permission.INTERNET"
/>
...
...
demo/src/main/java/com/google/android/exoplayer/demo/full/EventLogger.java
View file @
79c2f535
...
...
@@ -80,9 +80,9 @@ public class EventLogger implements DemoPlayer.Listener, DemoPlayer.InfoListener
// DemoPlayer.InfoListener
@Override
public
void
onBandwidthSample
(
int
elapsedMs
,
long
bytes
,
long
b
andwidth
Estimate
)
{
public
void
onBandwidthSample
(
int
elapsedMs
,
long
bytes
,
long
b
itrate
Estimate
)
{
Log
.
d
(
TAG
,
"bandwidth ["
+
getSessionTimeString
()
+
", "
+
bytes
+
", "
+
getTimeString
(
elapsedMs
)
+
", "
+
b
andwidth
Estimate
+
"]"
);
", "
+
getTimeString
(
elapsedMs
)
+
", "
+
b
itrate
Estimate
+
"]"
);
}
@Override
...
...
@@ -92,7 +92,7 @@ public class EventLogger implements DemoPlayer.Listener, DemoPlayer.InfoListener
@Override
public
void
onLoadStarted
(
int
sourceId
,
String
formatId
,
int
trigger
,
boolean
isInitialization
,
int
mediaStartTimeMs
,
int
mediaEndTimeMs
,
long
totalBytes
)
{
int
mediaStartTimeMs
,
int
mediaEndTimeMs
,
long
length
)
{
loadStartTimeMs
[
sourceId
]
=
SystemClock
.
elapsedRealtime
();
if
(
VerboseLogUtil
.
isTagEnabled
(
TAG
))
{
Log
.
v
(
TAG
,
"loadStart ["
+
getSessionTimeString
()
+
", "
+
sourceId
...
...
@@ -101,7 +101,7 @@ public class EventLogger implements DemoPlayer.Listener, DemoPlayer.InfoListener
}
@Override
public
void
onLoadCompleted
(
int
sourceId
)
{
public
void
onLoadCompleted
(
int
sourceId
,
long
bytesLoaded
)
{
if
(
VerboseLogUtil
.
isTagEnabled
(
TAG
))
{
long
downloadTime
=
SystemClock
.
elapsedRealtime
()
-
loadStartTimeMs
[
sourceId
];
Log
.
v
(
TAG
,
"loadEnd ["
+
getSessionTimeString
()
+
", "
+
sourceId
+
", "
+
...
...
demo/src/main/java/com/google/android/exoplayer/demo/full/player/DebugTrackRenderer.java
View file @
79c2f535
...
...
@@ -98,12 +98,12 @@ import android.widget.TextView;
@Override
protected
long
getDurationUs
()
{
return
TrackRenderer
.
MATCH_LONGEST
;
return
TrackRenderer
.
MATCH_LONGEST
_US
;
}
@Override
protected
long
getBufferedPositionUs
()
{
return
TrackRenderer
.
END_OF_TRACK
;
return
TrackRenderer
.
END_OF_TRACK
_US
;
}
@Override
...
...
demo/src/main/java/com/google/android/exoplayer/demo/full/player/DemoPlayer.java
View file @
79c2f535
...
...
@@ -121,10 +121,10 @@ public class DemoPlayer implements ExoPlayer.Listener, ChunkSampleSource.EventLi
void
onVideoFormatEnabled
(
String
formatId
,
int
trigger
,
int
mediaTimeMs
);
void
onAudioFormatEnabled
(
String
formatId
,
int
trigger
,
int
mediaTimeMs
);
void
onDroppedFrames
(
int
count
,
long
elapsed
);
void
onBandwidthSample
(
int
elapsedMs
,
long
bytes
,
long
b
andwidth
Estimate
);
void
onBandwidthSample
(
int
elapsedMs
,
long
bytes
,
long
b
itrate
Estimate
);
void
onLoadStarted
(
int
sourceId
,
String
formatId
,
int
trigger
,
boolean
isInitialization
,
int
mediaStartTimeMs
,
int
mediaEndTimeMs
,
long
totalBytes
);
void
onLoadCompleted
(
int
sourceId
);
int
mediaStartTimeMs
,
int
mediaEndTimeMs
,
long
length
);
void
onLoadCompleted
(
int
sourceId
,
long
bytesLoaded
);
}
/**
...
...
@@ -391,9 +391,9 @@ public class DemoPlayer implements ExoPlayer.Listener, ChunkSampleSource.EventLi
}
@Override
public
void
onBandwidthSample
(
int
elapsedMs
,
long
bytes
,
long
b
andwidth
Estimate
)
{
public
void
onBandwidthSample
(
int
elapsedMs
,
long
bytes
,
long
b
itrate
Estimate
)
{
if
(
infoListener
!=
null
)
{
infoListener
.
onBandwidthSample
(
elapsedMs
,
bytes
,
b
andwidth
Estimate
);
infoListener
.
onBandwidthSample
(
elapsedMs
,
bytes
,
b
itrate
Estimate
);
}
}
...
...
@@ -471,34 +471,34 @@ public class DemoPlayer implements ExoPlayer.Listener, ChunkSampleSource.EventLi
@Override
public
void
onLoadStarted
(
int
sourceId
,
String
formatId
,
int
trigger
,
boolean
isInitialization
,
int
mediaStartTimeMs
,
int
mediaEndTimeMs
,
long
totalBytes
)
{
int
mediaStartTimeMs
,
int
mediaEndTimeMs
,
long
length
)
{
if
(
infoListener
!=
null
)
{
infoListener
.
onLoadStarted
(
sourceId
,
formatId
,
trigger
,
isInitialization
,
mediaStartTimeMs
,
mediaEndTimeMs
,
totalBytes
);
mediaEndTimeMs
,
length
);
}
}
@Override
public
void
onLoadCompleted
(
int
sourceId
)
{
public
void
onLoadCompleted
(
int
sourceId
,
long
bytesLoaded
)
{
if
(
infoListener
!=
null
)
{
infoListener
.
onLoadCompleted
(
sourceId
);
infoListener
.
onLoadCompleted
(
sourceId
,
bytesLoaded
);
}
}
@Override
public
void
onLoadCanceled
(
int
sourceId
)
{
public
void
onLoadCanceled
(
int
sourceId
,
long
bytesLoaded
)
{
// Do nothing.
}
@Override
public
void
onUpstreamDiscarded
(
int
sourceId
,
int
mediaStartTimeMs
,
int
mediaEndTimeMs
,
long
totalBytes
)
{
long
bytesDiscarded
)
{
// Do nothing.
}
@Override
public
void
onDownstreamDiscarded
(
int
sourceId
,
int
mediaStartTimeMs
,
int
mediaEndTimeMs
,
long
totalBytes
)
{
long
bytesDiscarded
)
{
// Do nothing.
}
...
...
library/build.gradle
View file @
79c2f535
...
...
@@ -36,3 +36,14 @@ android {
dependencies
{
}
android
.
libraryVariants
.
all
{
variant
->
def
name
=
variant
.
buildType
.
name
if
(
name
.
equals
(
com
.
android
.
builder
.
core
.
BuilderConstants
.
DEBUG
))
{
return
;
// Skip debug builds.
}
def
task
=
project
.
tasks
.
create
"jar${name.capitalize()}"
,
Jar
task
.
dependsOn
variant
.
javaCompile
task
.
from
variant
.
javaCompile
.
destinationDir
artifacts
.
add
(
'archives'
,
task
);
}
library/src/main/java/com/google/android/exoplayer/C.java
0 → 100644
View file @
79c2f535
/*
* 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
;
/**
* Defines constants that are generally useful throughout the library.
*/
public
final
class
C
{
/**
* Represents an unbounded length of data.
*/
public
static
final
int
LENGTH_UNBOUNDED
=
-
1
;
private
C
()
{}
}
library/src/main/java/com/google/android/exoplayer/CodecCounters.java
View file @
79c2f535
...
...
@@ -17,54 +17,41 @@ package com.google.android.exoplayer;
/**
* Maintains codec event counts, for debugging purposes only.
* <p>
* Counters should be written from the playback thread only. Counters may be read from any thread.
* To ensure that the counter values are correctly reflected between threads, users of this class
* should invoke {@link #ensureUpdated()} prior to reading and after writing.
*/
public
final
class
CodecCounters
{
public
volatile
long
codecInitCount
;
public
volatile
long
codecReleaseCount
;
public
volatile
long
outputFormatChangedCount
;
public
volatile
long
outputBuffersChangedCount
;
public
volatile
long
queuedInputBufferCount
;
public
volatile
long
inputBufferWaitingForSampleCount
;
public
volatile
long
keyframeCount
;
public
volatile
long
queuedEndOfStreamCount
;
public
volatile
long
renderedOutputBufferCount
;
public
volatile
long
skippedOutputBufferCount
;
public
volatile
long
droppedOutputBufferCount
;
public
volatile
long
discardedSamplesCount
;
public
int
codecInitCount
;
public
int
codecReleaseCount
;
public
int
outputFormatChangedCount
;
public
int
outputBuffersChangedCount
;
public
int
renderedOutputBufferCount
;
public
int
skippedOutputBufferCount
;
public
int
droppedOutputBufferCount
;
/**
* Resets all counts to zero.
* Should be invoked from the playback thread after the counters have been updated. Should also
* be invoked from any other thread that wishes to read the counters, before reading. These calls
* ensure that counter updates are made visible to the reading threads.
*/
public
void
zeroAllCounts
()
{
codecInitCount
=
0
;
codecReleaseCount
=
0
;
outputFormatChangedCount
=
0
;
outputBuffersChangedCount
=
0
;
queuedInputBufferCount
=
0
;
inputBufferWaitingForSampleCount
=
0
;
keyframeCount
=
0
;
queuedEndOfStreamCount
=
0
;
renderedOutputBufferCount
=
0
;
skippedOutputBufferCount
=
0
;
droppedOutputBufferCount
=
0
;
discardedSamplesCount
=
0
;
public
synchronized
void
ensureUpdated
()
{
// Do nothing. The use of synchronized ensures a memory barrier should another thread also
// call this method.
}
public
String
getDebugString
()
{
ensureUpdated
();
StringBuilder
builder
=
new
StringBuilder
();
builder
.
append
(
"cic("
).
append
(
codecInitCount
).
append
(
")"
);
builder
.
append
(
"crc("
).
append
(
codecReleaseCount
).
append
(
")"
);
builder
.
append
(
"ofc("
).
append
(
outputFormatChangedCount
).
append
(
")"
);
builder
.
append
(
"obc("
).
append
(
outputBuffersChangedCount
).
append
(
")"
);
builder
.
append
(
"qib("
).
append
(
queuedInputBufferCount
).
append
(
")"
);
builder
.
append
(
"wib("
).
append
(
inputBufferWaitingForSampleCount
).
append
(
")"
);
builder
.
append
(
"kfc("
).
append
(
keyframeCount
).
append
(
")"
);
builder
.
append
(
"qes("
).
append
(
queuedEndOfStreamCount
).
append
(
")"
);
builder
.
append
(
"ren("
).
append
(
renderedOutputBufferCount
).
append
(
")"
);
builder
.
append
(
"sob("
).
append
(
skippedOutputBufferCount
).
append
(
")"
);
builder
.
append
(
"dob("
).
append
(
droppedOutputBufferCount
).
append
(
")"
);
builder
.
append
(
"dsc("
).
append
(
discardedSamplesCount
).
append
(
")"
);
return
builder
.
toString
();
}
...
...
library/src/main/java/com/google/android/exoplayer/ExoPlayer.java
View file @
79c2f535
...
...
@@ -316,14 +316,16 @@ public interface ExoPlayer {
public
void
seekTo
(
int
positionMs
);
/**
* Stops playback.
* Stops playback. Use {@code setPlayWhenReady(false)} rather than this method if the intention
* is to pause playback.
* <p>
* Calling this method will cause the playback state to transition to
* {@link ExoPlayer#STATE_IDLE}.
Note that the player instance can still be used, and that
* {@link ExoPlayer#release()} must still be called on the player
should it no longer be
required.
* {@link ExoPlayer#STATE_IDLE}.
The player instance can still be used, and
* {@link ExoPlayer#release()} must still be called on the player
if it's no longer
required.
* <p>
* Use {@code setPlayWhenReady(false)} rather than this method if the intention is to pause
* playback.
* Calling this method does not reset the playback position. If this player instance will be used
* to play another video from its start, then {@code seekTo(0)} should be called after stopping
* the player and before preparing it for the next video.
*/
public
void
stop
();
...
...
library/src/main/java/com/google/android/exoplayer/ExoPlayerImplInternal.java
View file @
79c2f535
...
...
@@ -60,7 +60,7 @@ import java.util.List;
private
static
final
int
IDLE_INTERVAL_MS
=
1000
;
private
final
Handler
handler
;
private
final
HandlerThread
internalPlay
er
Thread
;
private
final
HandlerThread
internalPlay
back
Thread
;
private
final
Handler
eventHandler
;
private
final
MediaClock
mediaClock
;
private
final
boolean
[]
rendererEnabledFlags
;
...
...
@@ -95,12 +95,12 @@ import java.util.List;
}
this
.
state
=
ExoPlayer
.
STATE_IDLE
;
this
.
durationUs
=
TrackRenderer
.
UNKNOWN_TIME
;
this
.
bufferedPositionUs
=
TrackRenderer
.
UNKNOWN_TIME
;
this
.
durationUs
=
TrackRenderer
.
UNKNOWN_TIME
_US
;
this
.
bufferedPositionUs
=
TrackRenderer
.
UNKNOWN_TIME
_US
;
mediaClock
=
new
MediaClock
();
enabledRenderers
=
new
ArrayList
<
TrackRenderer
>(
rendererEnabledFlags
.
length
);
internalPlay
er
Thread
=
new
HandlerThread
(
getClass
().
getSimpleName
()
+
":Handler"
)
{
internalPlay
back
Thread
=
new
HandlerThread
(
getClass
().
getSimpleName
()
+
":Handler"
)
{
@Override
public
void
run
()
{
// Note: The documentation for Process.THREAD_PRIORITY_AUDIO that states "Applications can
...
...
@@ -109,12 +109,12 @@ import java.util.List;
super
.
run
();
}
};
internalPlay
er
Thread
.
start
();
handler
=
new
Handler
(
internalPlay
er
Thread
.
getLooper
(),
this
);
internalPlay
back
Thread
.
start
();
handler
=
new
Handler
(
internalPlay
back
Thread
.
getLooper
(),
this
);
}
public
Looper
getPlaybackLooper
()
{
return
internalPlay
er
Thread
.
getLooper
();
return
internalPlay
back
Thread
.
getLooper
();
}
public
int
getCurrentPosition
()
{
...
...
@@ -122,12 +122,12 @@ import java.util.List;
}
public
int
getBufferedPosition
()
{
return
bufferedPositionUs
==
TrackRenderer
.
UNKNOWN_TIME
?
ExoPlayer
.
UNKNOWN_TIME
return
bufferedPositionUs
==
TrackRenderer
.
UNKNOWN_TIME
_US
?
ExoPlayer
.
UNKNOWN_TIME
:
(
int
)
(
bufferedPositionUs
/
1000
);
}
public
int
getDuration
()
{
return
durationUs
==
TrackRenderer
.
UNKNOWN_TIME
?
ExoPlayer
.
UNKNOWN_TIME
return
durationUs
==
TrackRenderer
.
UNKNOWN_TIME
_US
?
ExoPlayer
.
UNKNOWN_TIME
:
(
int
)
(
durationUs
/
1000
);
}
...
...
@@ -179,7 +179,7 @@ import java.util.List;
Thread
.
currentThread
().
interrupt
();
}
}
internalPlay
er
Thread
.
quit
();
internalPlay
back
Thread
.
quit
();
}
}
...
...
@@ -287,14 +287,14 @@ import java.util.List;
enabledRenderers
.
add
(
renderer
);
isEnded
=
isEnded
&&
renderer
.
isEnded
();
allRenderersReadyOrEnded
=
allRenderersReadyOrEnded
&&
rendererReadyOrEnded
(
renderer
);
if
(
durationUs
==
TrackRenderer
.
UNKNOWN_TIME
)
{
if
(
durationUs
==
TrackRenderer
.
UNKNOWN_TIME
_US
)
{
// We've already encountered a track for which the duration is unknown, so the media
// duration is unknown regardless of the duration of this track.
}
else
{
long
trackDurationUs
=
renderer
.
getDurationUs
();
if
(
trackDurationUs
==
TrackRenderer
.
UNKNOWN_TIME
)
{
durationUs
=
TrackRenderer
.
UNKNOWN_TIME
;
}
else
if
(
trackDurationUs
==
TrackRenderer
.
MATCH_LONGEST
)
{
if
(
trackDurationUs
==
TrackRenderer
.
UNKNOWN_TIME
_US
)
{
durationUs
=
TrackRenderer
.
UNKNOWN_TIME
_US
;
}
else
if
(
trackDurationUs
==
TrackRenderer
.
MATCH_LONGEST
_US
)
{
// Do nothing.
}
else
{
durationUs
=
Math
.
max
(
durationUs
,
trackDurationUs
);
...
...
@@ -331,11 +331,11 @@ import java.util.List;
long
rendererBufferedPositionUs
=
renderer
.
getBufferedPositionUs
();
long
minBufferDurationUs
=
rebuffering
?
minRebufferUs
:
minBufferUs
;
return
minBufferDurationUs
<=
0
||
rendererBufferedPositionUs
==
TrackRenderer
.
UNKNOWN_TIME
||
rendererBufferedPositionUs
==
TrackRenderer
.
END_OF_TRACK
||
rendererBufferedPositionUs
==
TrackRenderer
.
UNKNOWN_TIME
_US
||
rendererBufferedPositionUs
==
TrackRenderer
.
END_OF_TRACK
_US
||
rendererBufferedPositionUs
>=
positionUs
+
minBufferDurationUs
||
(
rendererDurationUs
!=
TrackRenderer
.
UNKNOWN_TIME
&&
rendererDurationUs
!=
TrackRenderer
.
MATCH_LONGEST
||
(
rendererDurationUs
!=
TrackRenderer
.
UNKNOWN_TIME
_US
&&
rendererDurationUs
!=
TrackRenderer
.
MATCH_LONGEST
_US
&&
rendererBufferedPositionUs
>=
rendererDurationUs
);
}
...
...
@@ -384,7 +384,7 @@ import java.util.List;
private
void
doSomeWork
()
throws
ExoPlaybackException
{
TraceUtil
.
beginSection
(
"doSomeWork"
);
long
operationStartTimeMs
=
SystemClock
.
elapsedRealtime
();
long
bufferedPositionUs
=
durationUs
!=
TrackRenderer
.
UNKNOWN_TIME
?
durationUs
long
bufferedPositionUs
=
durationUs
!=
TrackRenderer
.
UNKNOWN_TIME
_US
?
durationUs
:
Long
.
MAX_VALUE
;
boolean
isEnded
=
true
;
boolean
allRenderersReadyOrEnded
=
true
;
...
...
@@ -398,17 +398,17 @@ import java.util.List;
isEnded
=
isEnded
&&
renderer
.
isEnded
();
allRenderersReadyOrEnded
=
allRenderersReadyOrEnded
&&
rendererReadyOrEnded
(
renderer
);
if
(
bufferedPositionUs
==
TrackRenderer
.
UNKNOWN_TIME
)
{
if
(
bufferedPositionUs
==
TrackRenderer
.
UNKNOWN_TIME
_US
)
{
// We've already encountered a track for which the buffered position is unknown. Hence the
// media buffer position unknown regardless of the buffered position of this track.
}
else
{
long
rendererDurationUs
=
renderer
.
getDurationUs
();
long
rendererBufferedPositionUs
=
renderer
.
getBufferedPositionUs
();
if
(
rendererBufferedPositionUs
==
TrackRenderer
.
UNKNOWN_TIME
)
{
bufferedPositionUs
=
TrackRenderer
.
UNKNOWN_TIME
;
}
else
if
(
rendererBufferedPositionUs
==
TrackRenderer
.
END_OF_TRACK
||
(
rendererDurationUs
!=
TrackRenderer
.
UNKNOWN_TIME
&&
rendererDurationUs
!=
TrackRenderer
.
MATCH_LONGEST
if
(
rendererBufferedPositionUs
==
TrackRenderer
.
UNKNOWN_TIME
_US
)
{
bufferedPositionUs
=
TrackRenderer
.
UNKNOWN_TIME
_US
;
}
else
if
(
rendererBufferedPositionUs
==
TrackRenderer
.
END_OF_TRACK
_US
||
(
rendererDurationUs
!=
TrackRenderer
.
UNKNOWN_TIME
_US
&&
rendererDurationUs
!=
TrackRenderer
.
MATCH_LONGEST
_US
&&
rendererBufferedPositionUs
>=
rendererDurationUs
))
{
// This track is fully buffered.
}
else
{
...
...
@@ -525,7 +525,7 @@ import java.util.List;
notifyAll
();
}
}
if
(
state
!=
ExoPlayer
.
STATE_IDLE
)
{
if
(
state
!=
ExoPlayer
.
STATE_IDLE
&&
state
!=
ExoPlayer
.
STATE_PREPARING
)
{
// The message may have caused something to change that now requires us to do work.
handler
.
sendEmptyMessage
(
MSG_DO_SOME_WORK
);
}
...
...
library/src/main/java/com/google/android/exoplayer/ExoPlayerLibraryInfo.java
View file @
79c2f535
...
...
@@ -26,7 +26,7 @@ public class ExoPlayerLibraryInfo {
/**
* The version of the library, expressed as a string.
*/
public
static
final
String
VERSION
=
"1.0.1
1
"
;
public
static
final
String
VERSION
=
"1.0.1
2
"
;
/**
* The version of the library, expressed as an integer.
...
...
@@ -34,7 +34,7 @@ public class ExoPlayerLibraryInfo {
* Three digits are used for each component of {@link #VERSION}. For example "1.2.3" has the
* corresponding integer version 1002003.
*/
public
static
final
int
VERSION_INT
=
100001
0
;
public
static
final
int
VERSION_INT
=
100001
2
;
/**
* Whether the library was compiled with {@link com.google.android.exoplayer.util.Assertions}
...
...
library/src/main/java/com/google/android/exoplayer/FrameworkSampleSource.java
View file @
79c2f535
...
...
@@ -67,12 +67,12 @@ public final class FrameworkSampleSource implements SampleSource {
extractor
=
new
MediaExtractor
();
extractor
.
setDataSource
(
context
,
uri
,
headers
);
trackStates
=
new
int
[
extractor
.
getTrackCount
()];
pendingDiscontinuities
=
new
boolean
[
extractor
.
getTrackCount
()
];
pendingDiscontinuities
=
new
boolean
[
trackStates
.
length
];
trackInfos
=
new
TrackInfo
[
trackStates
.
length
];
for
(
int
i
=
0
;
i
<
trackStates
.
length
;
i
++)
{
android
.
media
.
MediaFormat
format
=
extractor
.
getTrackFormat
(
i
);
long
duration
=
format
.
containsKey
(
android
.
media
.
MediaFormat
.
KEY_DURATION
)
?
format
.
getLong
(
android
.
media
.
MediaFormat
.
KEY_DURATION
)
:
TrackRenderer
.
UNKNOWN_TIME
;
format
.
getLong
(
android
.
media
.
MediaFormat
.
KEY_DURATION
)
:
TrackRenderer
.
UNKNOWN_TIME
_US
;
String
mime
=
format
.
getString
(
android
.
media
.
MediaFormat
.
KEY_MIME
);
trackInfos
[
i
]
=
new
TrackInfo
(
mime
,
duration
);
}
...
...
@@ -84,7 +84,7 @@ public final class FrameworkSampleSource implements SampleSource {
@Override
public
int
getTrackCount
()
{
Assertions
.
checkState
(
prepared
);
return
extractor
.
getTrackCount
()
;
return
trackStates
.
length
;
}
@Override
...
...
@@ -97,17 +97,18 @@ public final class FrameworkSampleSource implements SampleSource {
public
void
enable
(
int
track
,
long
timeUs
)
{
Assertions
.
checkState
(
prepared
);
Assertions
.
checkState
(
trackStates
[
track
]
==
TRACK_STATE_DISABLED
);
boolean
wasSourceEnabled
=
isEnabled
();
trackStates
[
track
]
=
TRACK_STATE_ENABLED
;
extractor
.
selectTrack
(
track
);
if
(!
wasSourceEnabled
)
{
seekToUs
(
timeUs
);
}
seekToUs
(
timeUs
);
}
@Override
public
void
continueBuffering
(
long
playbackPositionUs
)
{
// Do nothing. The MediaExtractor instance is responsible for buffering.
public
boolean
continueBuffering
(
long
playbackPositionUs
)
{
// MediaExtractor takes care of buffering and blocks until it has samples, so we can always
// return true here. Although note that the blocking behavior is itself as bug, as per the
// TODO further up this file. This method will need to return something else as part of fixing
// the TODO.
return
true
;
}
@Override
...
...
@@ -122,15 +123,15 @@ public final class FrameworkSampleSource implements SampleSource {
if
(
onlyReadDiscontinuity
)
{
return
NOTHING_READ
;
}
if
(
trackStates
[
track
]
!=
TRACK_STATE_FORMAT_SENT
)
{
formatHolder
.
format
=
MediaFormat
.
createFromFrameworkMediaFormatV16
(
extractor
.
getTrackFormat
(
track
));
formatHolder
.
drmInitData
=
Util
.
SDK_INT
>=
18
?
getPsshInfoV18
()
:
null
;
trackStates
[
track
]
=
TRACK_STATE_FORMAT_SENT
;
return
FORMAT_READ
;
}
int
extractorTrackIndex
=
extractor
.
getSampleTrackIndex
();
if
(
extractorTrackIndex
==
track
)
{
if
(
trackStates
[
track
]
!=
TRACK_STATE_FORMAT_SENT
)
{
formatHolder
.
format
=
MediaFormat
.
createFromFrameworkMediaFormatV16
(
extractor
.
getTrackFormat
(
track
));
formatHolder
.
drmInitData
=
Util
.
SDK_INT
>=
18
?
getPsshInfoV18
()
:
null
;
trackStates
[
track
]
=
TRACK_STATE_FORMAT_SENT
;
return
FORMAT_READ
;
}
if
(
sampleHolder
.
data
!=
null
)
{
int
offset
=
sampleHolder
.
data
.
position
();
sampleHolder
.
size
=
extractor
.
readSampleData
(
sampleHolder
.
data
,
offset
);
...
...
@@ -187,7 +188,7 @@ public final class FrameworkSampleSource implements SampleSource {
Assertions
.
checkState
(
prepared
);
long
bufferedDurationUs
=
extractor
.
getCachedDuration
();
if
(
bufferedDurationUs
==
-
1
)
{
return
TrackRenderer
.
UNKNOWN_TIME
;
return
TrackRenderer
.
UNKNOWN_TIME
_US
;
}
else
{
return
extractor
.
getSampleTime
()
+
bufferedDurationUs
;
}
...
...
@@ -202,13 +203,4 @@ public final class FrameworkSampleSource implements SampleSource {
}
}
private
boolean
isEnabled
()
{
for
(
int
i
=
0
;
i
<
trackStates
.
length
;
i
++)
{
if
(
trackStates
[
i
]
!=
TRACK_STATE_DISABLED
)
{
return
true
;
}
}
return
false
;
}
}
library/src/main/java/com/google/android/exoplayer/MediaCodecAudioTrackRenderer.java
View file @
79c2f535
...
...
@@ -266,8 +266,6 @@ public class MediaCodecAudioTrackRenderer extends MediaCodecTrackRenderer {
@Override
protected
void
onOutputFormatChanged
(
MediaFormat
format
)
{
releaseAudioTrack
();
this
.
sampleRate
=
format
.
getInteger
(
MediaFormat
.
KEY_SAMPLE_RATE
);
int
channelCount
=
format
.
getInteger
(
MediaFormat
.
KEY_CHANNEL_COUNT
);
int
channelConfig
;
switch
(
channelCount
)
{
...
...
@@ -283,6 +281,16 @@ public class MediaCodecAudioTrackRenderer extends MediaCodecTrackRenderer {
default
:
throw
new
IllegalArgumentException
(
"Unsupported channel count: "
+
channelCount
);
}
int
sampleRate
=
format
.
getInteger
(
MediaFormat
.
KEY_SAMPLE_RATE
);
if
(
audioTrack
!=
null
&&
this
.
sampleRate
==
sampleRate
&&
this
.
channelConfig
==
channelConfig
)
{
// We already have an existing audio track with the correct sample rate and channel config.
return
;
}
releaseAudioTrack
();
this
.
sampleRate
=
sampleRate
;
this
.
channelConfig
=
channelConfig
;
this
.
minBufferSize
=
AudioTrack
.
getMinBufferSize
(
sampleRate
,
channelConfig
,
AudioFormat
.
ENCODING_PCM_16BIT
);
...
...
@@ -417,7 +425,7 @@ public class MediaCodecAudioTrackRenderer extends MediaCodecTrackRenderer {
@Override
protected
boolean
isReady
()
{
return
getPendingFrameCount
()
>
0
;
return
super
.
isReady
()
||
getPendingFrameCount
()
>
0
;
}
/**
...
...
library/src/main/java/com/google/android/exoplayer/MediaCodecTrackRenderer.java
View file @
79c2f535
...
...
@@ -128,6 +128,7 @@ public abstract class MediaCodecTrackRenderer extends TrackRenderer {
private
int
codecReconfigurationState
;
private
int
trackIndex
;
private
boolean
sourceIsReady
;
private
boolean
inputStreamEnded
;
private
boolean
outputStreamEnded
;
private
boolean
waitingForKeys
;
...
...
@@ -186,7 +187,12 @@ public abstract class MediaCodecTrackRenderer extends TrackRenderer {
return
TrackRenderer
.
STATE_IGNORE
;
}
@SuppressWarnings
(
"unused"
)
/**
* Determines whether a mime type is handled by the renderer.
*
* @param mimeType The mime type to test.
* @return True if the renderer can handle the mime type. False otherwise.
*/
protected
boolean
handlesMimeType
(
String
mimeType
)
{
return
true
;
// TODO: Uncomment once the TODO above is fixed.
...
...
@@ -196,6 +202,7 @@ public abstract class MediaCodecTrackRenderer extends TrackRenderer {
@Override
protected
void
onEnabled
(
long
timeUs
,
boolean
joining
)
{
source
.
enable
(
trackIndex
,
timeUs
);
sourceIsReady
=
false
;
inputStreamEnded
=
false
;
outputStreamEnded
=
false
;
waitingForKeys
=
false
;
...
...
@@ -280,14 +287,20 @@ public abstract class MediaCodecTrackRenderer extends TrackRenderer {
@Override
protected
void
onDisabled
()
{
releaseCodec
();
format
=
null
;
drmInitData
=
null
;
if
(
openedDrmSession
)
{
drmSessionManager
.
close
();
openedDrmSession
=
false
;
try
{
releaseCodec
();
}
finally
{
try
{
if
(
openedDrmSession
)
{
drmSessionManager
.
close
();
openedDrmSession
=
false
;
}
}
finally
{
source
.
disable
(
trackIndex
);
}
}
source
.
disable
(
trackIndex
);
}
protected
void
releaseCodec
()
{
...
...
@@ -332,7 +345,7 @@ public abstract class MediaCodecTrackRenderer extends TrackRenderer {
@Override
protected
long
getBufferedPositionUs
()
{
long
sourceBufferedPosition
=
source
.
getBufferedPositionUs
();
return
sourceBufferedPosition
==
UNKNOWN_TIME
||
sourceBufferedPosition
==
END_OF_TRACK
return
sourceBufferedPosition
==
UNKNOWN_TIME
_US
||
sourceBufferedPosition
==
END_OF_TRACK_US
?
sourceBufferedPosition
:
Math
.
max
(
sourceBufferedPosition
,
getCurrentPositionUs
());
}
...
...
@@ -340,6 +353,7 @@ public abstract class MediaCodecTrackRenderer extends TrackRenderer {
protected
void
seekTo
(
long
timeUs
)
throws
ExoPlaybackException
{
currentPositionUs
=
timeUs
;
source
.
seekToUs
(
timeUs
);
sourceIsReady
=
false
;
inputStreamEnded
=
false
;
outputStreamEnded
=
false
;
waitingForKeys
=
false
;
...
...
@@ -358,7 +372,7 @@ public abstract class MediaCodecTrackRenderer extends TrackRenderer {
@Override
protected
void
doSomeWork
(
long
timeUs
)
throws
ExoPlaybackException
{
try
{
source
.
continueBuffering
(
timeUs
);
source
IsReady
=
source
.
continueBuffering
(
timeUs
);
checkForDiscontinuity
();
if
(
format
==
null
)
{
readFormat
();
...
...
@@ -373,6 +387,7 @@ public abstract class MediaCodecTrackRenderer extends TrackRenderer {
while
(
feedInputBuffer
())
{}
}
}
codecCounters
.
ensureUpdated
();
}
catch
(
IOException
e
)
{
throw
new
ExoPlaybackException
(
e
);
}
...
...
@@ -394,7 +409,6 @@ public abstract class MediaCodecTrackRenderer extends TrackRenderer {
if
(!
sampleHolder
.
decodeOnly
)
{
currentPositionUs
=
sampleHolder
.
timeUs
;
}
codecCounters
.
discardedSamplesCount
++;
}
else
if
(
result
==
SampleSource
.
FORMAT_READ
)
{
onInputFormatChanged
(
formatHolder
);
}
...
...
@@ -467,7 +481,6 @@ public abstract class MediaCodecTrackRenderer extends TrackRenderer {
}
if
(
result
==
SampleSource
.
NOTHING_READ
)
{
codecCounters
.
inputBufferWaitingForSampleCount
++;
return
false
;
}
if
(
result
==
SampleSource
.
DISCONTINUITY_READ
)
{
...
...
@@ -496,7 +509,6 @@ public abstract class MediaCodecTrackRenderer extends TrackRenderer {
try
{
codec
.
queueInputBuffer
(
inputIndex
,
0
,
0
,
0
,
MediaCodec
.
BUFFER_FLAG_END_OF_STREAM
);
inputIndex
=
-
1
;
codecCounters
.
queuedEndOfStreamCount
++;
}
catch
(
CryptoException
e
)
{
notifyCryptoError
(
e
);
throw
new
ExoPlaybackException
(
e
);
...
...
@@ -536,10 +548,6 @@ public abstract class MediaCodecTrackRenderer extends TrackRenderer {
}
else
{
codec
.
queueInputBuffer
(
inputIndex
,
0
,
bufferSize
,
presentationTimeUs
,
0
);
}
codecCounters
.
queuedInputBufferCount
++;
if
((
sampleHolder
.
flags
&
MediaExtractor
.
SAMPLE_FLAG_SYNC
)
!=
0
)
{
codecCounters
.
keyframeCount
++;
}
inputIndex
=
-
1
;
codecReconfigurationState
=
RECONFIGURATION_STATE_NONE
;
}
catch
(
CryptoException
e
)
{
...
...
@@ -625,7 +633,6 @@ public abstract class MediaCodecTrackRenderer extends TrackRenderer {
* @param newFormat The new format.
* @return True if the existing instance can be reconfigured. False otherwise.
*/
@SuppressWarnings
(
"unused"
)
protected
boolean
canReconfigureCodec
(
MediaCodec
codec
,
boolean
codecIsAdaptive
,
MediaFormat
oldFormat
,
MediaFormat
newFormat
)
{
return
false
;
...
...
@@ -639,10 +646,7 @@ public abstract class MediaCodecTrackRenderer extends TrackRenderer {
@Override
protected
boolean
isReady
()
{
return
format
!=
null
&&
!
waitingForKeys
&&
((
codec
==
null
&&
!
shouldInitCodec
())
// We don't want the codec
||
outputIndex
>=
0
// Or we have an output buffer ready to release
||
inputIndex
<
0
// Or we don't have any input buffers to write to
||
isWithinHotswapPeriod
());
// Or the codec is being hotswapped
&&
(
sourceIsReady
||
outputIndex
>=
0
||
isWithinHotswapPeriod
());
}
private
boolean
isWithinHotswapPeriod
()
{
...
...
library/src/main/java/com/google/android/exoplayer/MediaCodecVideoTrackRenderer.java
View file @
79c2f535
...
...
@@ -235,7 +235,7 @@ public class MediaCodecVideoTrackRenderer extends MediaCodecTrackRenderer {
@Override
protected
boolean
isReady
()
{
if
(
super
.
isReady
()
&&
(
renderedFirstFrame
||
!
codecInitialized
())
)
{
if
(
super
.
isReady
())
{
// Ready. If we were joining then we've now joined, so clear the joining deadline.
joiningDeadlineUs
=
-
1
;
return
true
;
...
...
library/src/main/java/com/google/android/exoplayer/MediaFormat.java
View file @
79c2f535
...
...
@@ -148,12 +148,25 @@ public class MediaFormat {
if
(
obj
==
null
||
getClass
()
!=
obj
.
getClass
())
{
return
false
;
}
MediaFormat
other
=
(
MediaFormat
)
obj
;
if
(
maxInputSize
!=
other
.
maxInputSize
||
width
!=
other
.
width
||
height
!=
other
.
height
||
maxWidth
!=
other
.
maxWidth
||
maxHeight
!=
other
.
maxHeight
||
channelCount
!=
other
.
channelCount
||
sampleRate
!=
other
.
sampleRate
||
!
Util
.
areEqual
(
mimeType
,
other
.
mimeType
)
||
initializationData
.
size
()
!=
other
.
initializationData
.
size
())
{
return
equalsInternal
((
MediaFormat
)
obj
,
false
);
}
public
boolean
equals
(
MediaFormat
other
,
boolean
ignoreMaxDimensions
)
{
if
(
this
==
other
)
{
return
true
;
}
if
(
other
==
null
)
{
return
false
;
}
return
equalsInternal
(
other
,
ignoreMaxDimensions
);
}
private
boolean
equalsInternal
(
MediaFormat
other
,
boolean
ignoreMaxDimensions
)
{
if
(
maxInputSize
!=
other
.
maxInputSize
||
width
!=
other
.
width
||
height
!=
other
.
height
||
(!
ignoreMaxDimensions
&&
(
maxWidth
!=
other
.
maxWidth
||
maxHeight
!=
other
.
maxHeight
))
||
channelCount
!=
other
.
channelCount
||
sampleRate
!=
other
.
sampleRate
||
!
Util
.
areEqual
(
mimeType
,
other
.
mimeType
)
||
initializationData
.
size
()
!=
other
.
initializationData
.
size
())
{
return
false
;
}
for
(
int
i
=
0
;
i
<
initializationData
.
size
();
i
++)
{
...
...
library/src/main/java/com/google/android/exoplayer/SampleSource.java
View file @
79c2f535
...
...
@@ -102,8 +102,11 @@ public interface SampleSource {
* Indicates to the source that it should still be buffering data.
*
* @param playbackPositionUs The current playback position.
* @return True if the source has available samples, or if the end of the stream has been reached.
* False if more data needs to be buffered for samples to become available.
* @throws IOException If an error occurred reading from the source.
*/
public
void
continueBuffering
(
long
playbackPositionUs
)
;
public
boolean
continueBuffering
(
long
playbackPositionUs
)
throws
IOException
;
/**
* Attempts to read either a sample, a new format or or a discontinuity from the source.
...
...
@@ -144,8 +147,8 @@ public interface SampleSource {
* This method should not be called until after the source has been successfully prepared.
*
* @return An estimate of the absolute position in micro-seconds up to which data is buffered,
* or {@link TrackRenderer#END_OF_TRACK} if data is buffered to the end of the stream, or
* {@link TrackRenderer#UNKNOWN_TIME} if no estimate is available.
* or {@link TrackRenderer#END_OF_TRACK
_US
} if data is buffered to the end of the stream, or
* {@link TrackRenderer#UNKNOWN_TIME
_US
} if no estimate is available.
*/
public
long
getBufferedPositionUs
();
...
...
library/src/main/java/com/google/android/exoplayer/TrackRenderer.java
View file @
79c2f535
...
...
@@ -67,16 +67,16 @@ public abstract class TrackRenderer implements ExoPlayerComponent {
/**
* Represents an unknown time or duration.
*/
public
static
final
long
UNKNOWN_TIME
=
-
1
;
public
static
final
long
UNKNOWN_TIME
_US
=
-
1
;
/**
* Represents a time or duration that should match the duration of the longest track whose
* duration is known.
*/
public
static
final
long
MATCH_LONGEST
=
-
2
;
public
static
final
long
MATCH_LONGEST
_US
=
-
2
;
/**
* Represents the time of the end of the track.
*/
public
static
final
long
END_OF_TRACK
=
-
3
;
public
static
final
long
END_OF_TRACK
_US
=
-
3
;
private
int
state
;
...
...
@@ -110,7 +110,6 @@ public abstract class TrackRenderer implements ExoPlayerComponent {
*
* @return The current state (one of the STATE_* constants), for convenience.
*/
@SuppressWarnings
(
"unused"
)
/* package */
final
int
prepare
()
throws
ExoPlaybackException
{
Assertions
.
checkState
(
state
==
TrackRenderer
.
STATE_UNPREPARED
);
state
=
doPrepare
();
...
...
@@ -301,9 +300,9 @@ public abstract class TrackRenderer implements ExoPlayerComponent {
* This method may be called when the renderer is in the following states:
* {@link #STATE_PREPARED}, {@link #STATE_ENABLED}, {@link #STATE_STARTED}
*
* @return The duration of the track in micro-seconds, or {@link #MATCH_LONGEST} if
* @return The duration of the track in micro-seconds, or {@link #MATCH_LONGEST
_US
} if
* the track's duration should match that of the longest track whose duration is known, or
* or {@link #UNKNOWN_TIME} if the duration is not known.
* or {@link #UNKNOWN_TIME
_US
} if the duration is not known.
*/
protected
abstract
long
getDurationUs
();
...
...
@@ -324,8 +323,8 @@ public abstract class TrackRenderer implements ExoPlayerComponent {
* {@link #STATE_ENABLED}, {@link #STATE_STARTED}
*
* @return An estimate of the absolute position in micro-seconds up to which data is buffered,
* or {@link #END_OF_TRACK
} if the track is fully buffered, or {@link #UNKNOWN_TIME} if no
* estimate is available.
* or {@link #END_OF_TRACK
_US} if the track is fully buffered, or {@link #UNKNOWN_TIME_US} if
*
no
estimate is available.
*/
protected
abstract
long
getBufferedPositionUs
();
...
...
library/src/main/java/com/google/android/exoplayer/chunk/Chunk.java
View file @
79c2f535
...
...
@@ -15,6 +15,7 @@
*/
package
com
.
google
.
android
.
exoplayer
.
chunk
;
import
com.google.android.exoplayer.C
;
import
com.google.android.exoplayer.upstream.Allocation
;
import
com.google.android.exoplayer.upstream.Allocator
;
import
com.google.android.exoplayer.upstream.DataSource
;
...
...
@@ -51,7 +52,7 @@ public abstract class Chunk implements Loadable {
/**
* @param dataSource The source from which the data should be loaded.
* @param dataSpec Defines the data to be loaded. {@code dataSpec.length} must not exceed
* {@link Integer#MAX_VALUE}. If {@code dataSpec.length ==
DataSpec
.LENGTH_UNBOUNDED} then
* {@link Integer#MAX_VALUE}. If {@code dataSpec.length ==
C
.LENGTH_UNBOUNDED} then
* the length resolved by {@code dataSource.open(dataSpec)} must not exceed
* {@link Integer#MAX_VALUE}.
* @param format See {@link #format}.
...
...
@@ -89,8 +90,8 @@ public abstract class Chunk implements Loadable {
/**
* Gets the length of the chunk in bytes.
*
* @return The length of the chunk in bytes, or {@
value DataSpec#LENGTH_UNBOUNDED} if the length
*
has yet
to be determined.
* @return The length of the chunk in bytes, or {@
link C#LENGTH_UNBOUNDED} if the length has yet
* to be determined.
*/
public
final
long
getLength
()
{
return
dataSourceStream
.
getLength
();
...
...
library/src/main/java/com/google/android/exoplayer/chunk/ChunkSampleSource.java
View file @
79c2f535
...
...
@@ -15,6 +15,7 @@
*/
package
com
.
google
.
android
.
exoplayer
.
chunk
;
import
com.google.android.exoplayer.C
;
import
com.google.android.exoplayer.FormatHolder
;
import
com.google.android.exoplayer.LoadControl
;
import
com.google.android.exoplayer.MediaFormat
;
...
...
@@ -22,7 +23,6 @@ import com.google.android.exoplayer.SampleHolder;
import
com.google.android.exoplayer.SampleSource
;
import
com.google.android.exoplayer.TrackInfo
;
import
com.google.android.exoplayer.TrackRenderer
;
import
com.google.android.exoplayer.upstream.DataSpec
;
import
com.google.android.exoplayer.upstream.Loader
;
import
com.google.android.exoplayer.util.Assertions
;
...
...
@@ -57,24 +57,27 @@ public class ChunkSampleSource implements SampleSource, Loader.Listener {
* load is for initialization data.
* @param mediaEndTimeMs The media time of the end of the data being loaded, or -1 if this
* load is for initialization data.
* @param totalBytes The length of the data being loaded in bytes.
* @param length The length of the data being loaded in bytes, or {@link C#LENGTH_UNBOUNDED} if
* the length of the data has not yet been determined.
*/
void
onLoadStarted
(
int
sourceId
,
String
formatId
,
int
trigger
,
boolean
isInitialization
,
int
mediaStartTimeMs
,
int
mediaEndTimeMs
,
long
totalBytes
);
int
mediaStartTimeMs
,
int
mediaEndTimeMs
,
long
length
);
/**
* Invoked when the current load operation completes.
*
* @param sourceId The id of the reporting {@link SampleSource}.
* @param bytesLoaded The number of bytes that were loaded.
*/
void
onLoadCompleted
(
int
sourceId
);
void
onLoadCompleted
(
int
sourceId
,
long
bytesLoaded
);
/**
* Invoked when the current upstream load operation is canceled.
*
* @param sourceId The id of the reporting {@link SampleSource}.
* @param bytesLoaded The number of bytes that were loaded prior to the cancellation.
*/
void
onLoadCanceled
(
int
sourceId
);
void
onLoadCanceled
(
int
sourceId
,
long
bytesLoaded
);
/**
* Invoked when data is removed from the back of the buffer, typically so that it can be
...
...
@@ -83,10 +86,10 @@ public class ChunkSampleSource implements SampleSource, Loader.Listener {
* @param sourceId The id of the reporting {@link SampleSource}.
* @param mediaStartTimeMs The media time of the start of the discarded data.
* @param mediaEndTimeMs The media time of the end of the discarded data.
* @param
totalBytes
The length of the data being discarded in bytes.
* @param
bytesDiscarded
The length of the data being discarded in bytes.
*/
void
onUpstreamDiscarded
(
int
sourceId
,
int
mediaStartTimeMs
,
int
mediaEndTimeMs
,
long
totalBytes
);
long
bytesDiscarded
);
/**
* Invoked when an error occurs loading media data.
...
...
@@ -111,10 +114,10 @@ public class ChunkSampleSource implements SampleSource, Loader.Listener {
* @param sourceId The id of the reporting {@link SampleSource}.
* @param mediaStartTimeMs The media time of the start of the discarded data.
* @param mediaEndTimeMs The media time of the end of the discarded data.
* @param
totalBytes
The length of the data being discarded in bytes.
* @param
bytesDiscarded
The length of the data being discarded in bytes.
*/
void
onDownstreamDiscarded
(
int
sourceId
,
int
mediaStartTimeMs
,
int
mediaEndTimeMs
,
long
totalBytes
);
long
bytesDiscarded
);
/**
* Invoked when the downstream format changes (i.e. when the format being supplied to the
...
...
@@ -246,11 +249,21 @@ public class ChunkSampleSource implements SampleSource, Loader.Listener {
}
@Override
public
void
continueBuffering
(
long
playbackPositionUs
)
{
public
boolean
continueBuffering
(
long
playbackPositionUs
)
throws
IOException
{
Assertions
.
checkState
(
state
==
STATE_ENABLED
);
downstreamPositionUs
=
playbackPositionUs
;
chunkSource
.
continueBuffering
(
playbackPositionUs
);
updateLoadControl
();
if
(
isPendingReset
()
||
mediaChunks
.
isEmpty
())
{
return
false
;
}
else
if
(
mediaChunks
.
getFirst
().
sampleAvailable
())
{
// There's a sample available to be read from the current chunk.
return
true
;
}
else
{
// It may be the case that the current chunk has been fully read but not yet discarded and
// that the next chunk has an available sample. Return true if so, otherwise false.
return
mediaChunks
.
size
()
>
1
&&
mediaChunks
.
get
(
1
).
sampleAvailable
();
}
}
@Override
...
...
@@ -309,7 +322,7 @@ public class ChunkSampleSource implements SampleSource, Loader.Listener {
}
MediaFormat
mediaFormat
=
mediaChunk
.
getMediaFormat
();
if
(
mediaFormat
!=
null
&&
!
mediaFormat
.
equals
(
downstreamMediaFormat
))
{
if
(
mediaFormat
!=
null
&&
!
mediaFormat
.
equals
(
downstreamMediaFormat
,
true
))
{
chunkSource
.
getMaxVideoDimensions
(
mediaFormat
);
formatHolder
.
format
=
mediaFormat
;
formatHolder
.
drmInitData
=
mediaChunk
.
getPsshInfo
();
...
...
@@ -373,14 +386,14 @@ public class ChunkSampleSource implements SampleSource, Loader.Listener {
if
(
currentLoadable
!=
null
&&
mediaChunk
==
currentLoadable
)
{
// Linearly interpolate partially-fetched chunk times.
long
chunkLength
=
mediaChunk
.
getLength
();
if
(
chunkLength
!=
DataSpec
.
LENGTH_UNBOUNDED
)
{
if
(
chunkLength
!=
C
.
LENGTH_UNBOUNDED
)
{
return
mediaChunk
.
startTimeUs
+
((
mediaChunk
.
endTimeUs
-
mediaChunk
.
startTimeUs
)
*
mediaChunk
.
bytesLoaded
())
/
chunkLength
;
}
else
{
return
mediaChunk
.
startTimeUs
;
}
}
else
if
(
mediaChunk
.
isLastChunk
())
{
return
TrackRenderer
.
END_OF_TRACK
;
return
TrackRenderer
.
END_OF_TRACK
_US
;
}
else
{
return
mediaChunk
.
endTimeUs
;
}
...
...
@@ -399,6 +412,7 @@ public class ChunkSampleSource implements SampleSource, Loader.Listener {
@Override
public
void
onLoaded
()
{
Chunk
currentLoadable
=
currentLoadableHolder
.
chunk
;
notifyLoadCompleted
(
currentLoadable
.
bytesLoaded
());
try
{
currentLoadable
.
consume
();
}
catch
(
IOException
e
)
{
...
...
@@ -414,7 +428,6 @@ public class ChunkSampleSource implements SampleSource, Loader.Listener {
if
(!
currentLoadableExceptionFatal
)
{
clearCurrentLoadable
();
}
notifyLoadCompleted
();
updateLoadControl
();
}
}
...
...
@@ -422,11 +435,11 @@ public class ChunkSampleSource implements SampleSource, Loader.Listener {
@Override
public
void
onCanceled
()
{
Chunk
currentLoadable
=
currentLoadableHolder
.
chunk
;
notifyLoadCanceled
(
currentLoadable
.
bytesLoaded
());
if
(!
isMediaChunk
(
currentLoadable
))
{
currentLoadable
.
release
();
}
clearCurrentLoadable
();
notifyLoadCanceled
();
if
(
state
==
STATE_ENABLED
)
{
restartFrom
(
pendingResetTime
);
}
else
{
...
...
@@ -667,35 +680,35 @@ public class ChunkSampleSource implements SampleSource, Loader.Listener {
private
void
notifyLoadStarted
(
final
String
formatId
,
final
int
trigger
,
final
boolean
isInitialization
,
final
long
mediaStartTimeUs
,
final
long
mediaEndTimeUs
,
final
long
totalBytes
)
{
final
long
length
)
{
if
(
eventHandler
!=
null
&&
eventListener
!=
null
)
{
eventHandler
.
post
(
new
Runnable
()
{
@Override
public
void
run
()
{
eventListener
.
onLoadStarted
(
eventSourceId
,
formatId
,
trigger
,
isInitialization
,
usToMs
(
mediaStartTimeUs
),
usToMs
(
mediaEndTimeUs
),
totalBytes
);
usToMs
(
mediaStartTimeUs
),
usToMs
(
mediaEndTimeUs
),
length
);
}
});
}
}
private
void
notifyLoadCompleted
()
{
private
void
notifyLoadCompleted
(
final
long
bytesLoaded
)
{
if
(
eventHandler
!=
null
&&
eventListener
!=
null
)
{
eventHandler
.
post
(
new
Runnable
()
{
@Override
public
void
run
()
{
eventListener
.
onLoadCompleted
(
eventSourceId
);
eventListener
.
onLoadCompleted
(
eventSourceId
,
bytesLoaded
);
}
});
}
}
private
void
notifyLoadCanceled
()
{
private
void
notifyLoadCanceled
(
final
long
bytesLoaded
)
{
if
(
eventHandler
!=
null
&&
eventListener
!=
null
)
{
eventHandler
.
post
(
new
Runnable
()
{
@Override
public
void
run
()
{
eventListener
.
onLoadCanceled
(
eventSourceId
);
eventListener
.
onLoadCanceled
(
eventSourceId
,
bytesLoaded
);
}
});
}
...
...
@@ -750,13 +763,13 @@ public class ChunkSampleSource implements SampleSource, Loader.Listener {
}
private
void
notifyDownstreamDiscarded
(
final
long
mediaStartTimeUs
,
final
long
mediaEndTimeUs
,
final
long
totalBytes
)
{
final
long
bytesDiscarded
)
{
if
(
eventHandler
!=
null
&&
eventListener
!=
null
)
{
eventHandler
.
post
(
new
Runnable
()
{
@Override
public
void
run
()
{
eventListener
.
onDownstreamDiscarded
(
eventSourceId
,
usToMs
(
mediaStartTimeUs
),
usToMs
(
mediaEndTimeUs
),
totalBytes
);
usToMs
(
mediaEndTimeUs
),
bytesDiscarded
);
}
});
}
...
...
library/src/main/java/com/google/android/exoplayer/chunk/Format.java
View file @
79c2f535
...
...
@@ -72,6 +72,14 @@ public class Format {
public
final
int
bitrate
;
/**
* The language of the format. Can be null if unknown.
* <p>
* The language codes are two-letter lowercase ISO language codes (such as "en") as defined by
* ISO 639-1.
*/
public
final
String
language
;
/**
* The average bandwidth in bytes per second.
*
* @deprecated Use {@link #bitrate}. However note that the units of measurement are different.
...
...
@@ -90,6 +98,21 @@ public class Format {
*/
public
Format
(
String
id
,
String
mimeType
,
int
width
,
int
height
,
int
numChannels
,
int
audioSamplingRate
,
int
bitrate
)
{
this
(
id
,
mimeType
,
width
,
height
,
numChannels
,
audioSamplingRate
,
bitrate
,
null
);
}
/**
* @param id The format identifier.
* @param mimeType The format mime type.
* @param width The width 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 audioSamplingRate The audio sampling rate in Hz, or -1 for non-audio formats.
* @param bitrate The average bandwidth of the format in bits per second.
* @param language The language of the format.
*/
public
Format
(
String
id
,
String
mimeType
,
int
width
,
int
height
,
int
numChannels
,
int
audioSamplingRate
,
int
bitrate
,
String
language
)
{
this
.
id
=
Assertions
.
checkNotNull
(
id
);
this
.
mimeType
=
mimeType
;
this
.
width
=
width
;
...
...
@@ -97,6 +120,7 @@ public class Format {
this
.
numChannels
=
numChannels
;
this
.
audioSamplingRate
=
audioSamplingRate
;
this
.
bitrate
=
bitrate
;
this
.
language
=
language
;
this
.
bandwidth
=
bitrate
/
8
;
}
...
...
library/src/main/java/com/google/android/exoplayer/chunk/FormatEvaluator.java
View file @
79c2f535
...
...
@@ -164,7 +164,7 @@ public interface FormatEvaluator {
*/
public
static
class
AdaptiveEvaluator
implements
FormatEvaluator
{
public
static
final
int
DEFAULT_MAX_INITIAL_B
YTE_RATE
=
1
00000
;
public
static
final
int
DEFAULT_MAX_INITIAL_B
ITRATE
=
8
00000
;
public
static
final
int
DEFAULT_MIN_DURATION_FOR_QUALITY_INCREASE_MS
=
10000
;
public
static
final
int
DEFAULT_MAX_DURATION_FOR_QUALITY_DECREASE_MS
=
25000
;
...
...
@@ -173,7 +173,7 @@ public interface FormatEvaluator {
private
final
BandwidthMeter
bandwidthMeter
;
private
final
int
maxInitialB
yteR
ate
;
private
final
int
maxInitialB
itr
ate
;
private
final
long
minDurationForQualityIncreaseUs
;
private
final
long
maxDurationForQualityDecreaseUs
;
private
final
long
minDurationToRetainAfterDiscardUs
;
...
...
@@ -183,7 +183,7 @@ public interface FormatEvaluator {
* @param bandwidthMeter Provides an estimate of the currently available bandwidth.
*/
public
AdaptiveEvaluator
(
BandwidthMeter
bandwidthMeter
)
{
this
(
bandwidthMeter
,
DEFAULT_MAX_INITIAL_B
YTE_
RATE
,
this
(
bandwidthMeter
,
DEFAULT_MAX_INITIAL_B
IT
RATE
,
DEFAULT_MIN_DURATION_FOR_QUALITY_INCREASE_MS
,
DEFAULT_MAX_DURATION_FOR_QUALITY_DECREASE_MS
,
DEFAULT_MIN_DURATION_TO_RETAIN_AFTER_DISCARD_MS
,
DEFAULT_BANDWIDTH_FRACTION
);
...
...
@@ -191,7 +191,7 @@ public interface FormatEvaluator {
/**
* @param bandwidthMeter Provides an estimate of the currently available bandwidth.
* @param maxInitialB
yteRate The maximum bandwidth in byte
s per second that should be assumed
* @param maxInitialB
itrate The maximum bitrate in bit
s per second that should be assumed
* when bandwidthMeter cannot provide an estimate due to playback having only just started.
* @param minDurationForQualityIncreaseMs The minimum duration of buffered data required for
* the evaluator to consider switching to a higher quality format.
...
...
@@ -206,13 +206,13 @@ public interface FormatEvaluator {
* for inaccuracies in the bandwidth estimator.
*/
public
AdaptiveEvaluator
(
BandwidthMeter
bandwidthMeter
,
int
maxInitialB
yteR
ate
,
int
maxInitialB
itr
ate
,
int
minDurationForQualityIncreaseMs
,
int
maxDurationForQualityDecreaseMs
,
int
minDurationToRetainAfterDiscardMs
,
float
bandwidthFraction
)
{
this
.
bandwidthMeter
=
bandwidthMeter
;
this
.
maxInitialB
yteRate
=
maxInitialByteR
ate
;
this
.
maxInitialB
itrate
=
maxInitialBitr
ate
;
this
.
minDurationForQualityIncreaseUs
=
minDurationForQualityIncreaseMs
*
1000L
;
this
.
maxDurationForQualityDecreaseUs
=
maxDurationForQualityDecreaseMs
*
1000L
;
this
.
minDurationToRetainAfterDiscardUs
=
minDurationToRetainAfterDiscardMs
*
1000L
;
...
...
@@ -235,7 +235,7 @@ public interface FormatEvaluator {
long
bufferedDurationUs
=
queue
.
isEmpty
()
?
0
:
queue
.
get
(
queue
.
size
()
-
1
).
endTimeUs
-
playbackPositionUs
;
Format
current
=
evaluation
.
format
;
Format
ideal
=
determineIdealFormat
(
formats
,
bandwidthMeter
.
getEstimate
());
Format
ideal
=
determineIdealFormat
(
formats
,
bandwidthMeter
.
get
Bitrate
Estimate
());
boolean
isHigher
=
ideal
!=
null
&&
current
!=
null
&&
ideal
.
bitrate
>
current
.
bitrate
;
boolean
isLower
=
ideal
!=
null
&&
current
!=
null
&&
ideal
.
bitrate
<
current
.
bitrate
;
if
(
isHigher
)
{
...
...
@@ -276,11 +276,11 @@ public interface FormatEvaluator {
/**
* Compute the ideal format ignoring buffer health.
*/
protected
Format
determineIdealFormat
(
Format
[]
formats
,
long
b
andwidth
Estimate
)
{
long
effectiveB
andwidth
=
computeEffectiveBandwidthEstimate
(
bandwidth
Estimate
);
protected
Format
determineIdealFormat
(
Format
[]
formats
,
long
b
itrate
Estimate
)
{
long
effectiveB
itrate
=
computeEffectiveBitrateEstimate
(
bitrate
Estimate
);
for
(
int
i
=
0
;
i
<
formats
.
length
;
i
++)
{
Format
format
=
formats
[
i
];
if
(
(
format
.
bitrate
/
8
)
<=
effectiveBandwidth
)
{
if
(
format
.
bitrate
<=
effectiveBitrate
)
{
return
format
;
}
}
...
...
@@ -291,9 +291,9 @@ public interface FormatEvaluator {
/**
* Apply overhead factor, or default value in absence of estimate.
*/
protected
long
computeEffectiveB
andwidthEstimate
(
long
bandwidth
Estimate
)
{
return
b
andwidth
Estimate
==
BandwidthMeter
.
NO_ESTIMATE
?
maxInitialB
yteRate
:
(
long
)
(
bandwidth
Estimate
*
bandwidthFraction
);
protected
long
computeEffectiveB
itrateEstimate
(
long
bitrate
Estimate
)
{
return
b
itrate
Estimate
==
BandwidthMeter
.
NO_ESTIMATE
?
maxInitialB
itrate
:
(
long
)
(
bitrate
Estimate
*
bandwidthFraction
);
}
}
...
...
library/src/main/java/com/google/android/exoplayer/chunk/MediaChunk.java
View file @
79c2f535
...
...
@@ -100,6 +100,14 @@ public abstract class MediaChunk extends Chunk {
public
abstract
boolean
prepare
()
throws
ParserException
;
/**
* Returns whether the next sample is available.
*
* @return True if the next sample is available for reading. False otherwise.
* @throws ParserException
*/
public
abstract
boolean
sampleAvailable
()
throws
ParserException
;
/**
* Reads the next media sample from the chunk.
* <p>
* Should only be called after the chunk has been successfully prepared.
...
...
library/src/main/java/com/google/android/exoplayer/chunk/Mp4MediaChunk.java
View file @
79c2f535
...
...
@@ -104,11 +104,18 @@ public final class Mp4MediaChunk extends MediaChunk {
}
@Override
public
boolean
sampleAvailable
()
throws
ParserException
{
NonBlockingInputStream
inputStream
=
getNonBlockingInputStream
();
int
result
=
extractor
.
read
(
inputStream
,
null
);
return
(
result
&
FragmentedMp4Extractor
.
RESULT_NEED_SAMPLE_HOLDER
)
!=
0
;
}
@Override
public
boolean
read
(
SampleHolder
holder
)
throws
ParserException
{
NonBlockingInputStream
inputStream
=
getNonBlockingInputStream
();
Assertions
.
checkState
(
inputStream
!=
null
);
int
result
=
extractor
.
read
(
inputStream
,
holder
);
boolean
sampleRead
=
(
result
&
FragmentedMp4Extractor
.
RESULT_READ_SAMPLE
_FULL
)
!=
0
;
boolean
sampleRead
=
(
result
&
FragmentedMp4Extractor
.
RESULT_READ_SAMPLE
)
!=
0
;
if
(
sampleRead
)
{
holder
.
timeUs
-=
sampleOffsetUs
;
}
...
...
library/src/main/java/com/google/android/exoplayer/chunk/SingleSampleMediaChunk.java
View file @
79c2f535
...
...
@@ -83,10 +83,15 @@ public class SingleSampleMediaChunk extends MediaChunk {
}
@Override
public
boolean
sampleAvailable
()
{
return
isLoadFinished
()
&&
!
isReadFinished
();
}
@Override
public
boolean
read
(
SampleHolder
holder
)
{
NonBlockingInputStream
inputStream
=
getNonBlockingInputStream
();
Assertions
.
checkState
(
inputStream
!=
null
);
if
(!
isLoadFinished
())
{
if
(!
sampleAvailable
())
{
return
false
;
}
int
bytesLoaded
=
(
int
)
bytesLoaded
();
...
...
library/src/main/java/com/google/android/exoplayer/chunk/WebmMediaChunk.java
View file @
79c2f535
...
...
@@ -16,6 +16,7 @@
package
com
.
google
.
android
.
exoplayer
.
chunk
;
import
com.google.android.exoplayer.MediaFormat
;
import
com.google.android.exoplayer.ParserException
;
import
com.google.android.exoplayer.SampleHolder
;
import
com.google.android.exoplayer.parser.webm.WebmExtractor
;
import
com.google.android.exoplayer.upstream.DataSource
;
...
...
@@ -70,10 +71,18 @@ public final class WebmMediaChunk extends MediaChunk {
}
@Override
public
boolean
sampleAvailable
()
throws
ParserException
{
NonBlockingInputStream
inputStream
=
getNonBlockingInputStream
();
int
result
=
extractor
.
read
(
inputStream
,
null
);
return
(
result
&
WebmExtractor
.
RESULT_NEED_SAMPLE_HOLDER
)
!=
0
;
}
@Override
public
boolean
read
(
SampleHolder
holder
)
{
NonBlockingInputStream
inputStream
=
getNonBlockingInputStream
();
Assertions
.
checkState
(
inputStream
!=
null
);
return
extractor
.
read
(
inputStream
,
holder
);
int
result
=
extractor
.
read
(
inputStream
,
holder
);
return
(
result
&
WebmExtractor
.
RESULT_READ_SAMPLE
)
!=
0
;
}
@Override
...
...
library/src/main/java/com/google/android/exoplayer/dash/DashMp4ChunkSource.java
View file @
79c2f535
...
...
@@ -146,7 +146,7 @@ public class DashMp4ChunkSource implements ChunkSource {
RangedUri
pendingInitializationUri
=
null
;
RangedUri
pendingIndexUri
=
null
;
if
(
extractor
.
get
Track
()
==
null
)
{
if
(
extractor
.
get
Format
()
==
null
)
{
pendingInitializationUri
=
selectedRepresentation
.
getInitializationUri
();
}
if
(!
segmentIndexes
.
containsKey
(
selectedRepresentation
.
format
.
id
))
{
...
...
@@ -199,10 +199,10 @@ public class DashMp4ChunkSource implements ChunkSource {
if
(
initializationUri
!=
null
)
{
// It's common for initialization and index data to be stored adjacently. Attempt to merge
// the two requests together to request both at once.
expectedExtractorResult
|=
FragmentedMp4Extractor
.
RESULT_READ_
MOOV
;
expectedExtractorResult
|=
FragmentedMp4Extractor
.
RESULT_READ_
INIT
;
requestUri
=
initializationUri
.
attemptMerge
(
indexUri
);
if
(
requestUri
!=
null
)
{
expectedExtractorResult
|=
FragmentedMp4Extractor
.
RESULT_READ_
SID
X
;
expectedExtractorResult
|=
FragmentedMp4Extractor
.
RESULT_READ_
INDE
X
;
indexAnchor
=
indexUri
.
start
+
indexUri
.
length
;
}
else
{
requestUri
=
initializationUri
;
...
...
@@ -210,7 +210,7 @@ public class DashMp4ChunkSource implements ChunkSource {
}
else
{
requestUri
=
indexUri
;
indexAnchor
=
indexUri
.
start
+
indexUri
.
length
;
expectedExtractorResult
|=
FragmentedMp4Extractor
.
RESULT_READ_
SID
X
;
expectedExtractorResult
|=
FragmentedMp4Extractor
.
RESULT_READ_
INDE
X
;
}
DataSpec
dataSpec
=
new
DataSpec
(
requestUri
.
getUri
(),
requestUri
.
start
,
requestUri
.
length
,
representation
.
getCacheKey
());
...
...
@@ -256,9 +256,9 @@ public class DashMp4ChunkSource implements ChunkSource {
throw
new
ParserException
(
"Invalid extractor result. Expected "
+
expectedExtractorResult
+
", got "
+
result
);
}
if
((
result
&
FragmentedMp4Extractor
.
RESULT_READ_
SID
X
)
!=
0
)
{
if
((
result
&
FragmentedMp4Extractor
.
RESULT_READ_
INDE
X
)
!=
0
)
{
segmentIndexes
.
put
(
format
.
id
,
new
DashWrappingSegmentIndex
(
extractor
.
get
Segment
Index
(),
uri
,
indexAnchor
));
new
DashWrappingSegmentIndex
(
extractor
.
getIndex
(),
uri
,
indexAnchor
));
}
}
...
...
library/src/main/java/com/google/android/exoplayer/dash/DashWebmChunkSource.java
View file @
79c2f535
...
...
@@ -56,21 +56,26 @@ public class DashWebmChunkSource implements ChunkSource {
private
final
Format
[]
formats
;
private
final
HashMap
<
String
,
Representation
>
representations
;
private
final
HashMap
<
String
,
WebmExtractor
>
extractors
;
private
final
HashMap
<
String
,
Default
WebmExtractor
>
extractors
;
private
final
HashMap
<
String
,
DashSegmentIndex
>
segmentIndexes
;
private
boolean
lastChunkWasInitialization
;
/**
* @param dataSource A {@link DataSource} suitable for loading the media data.
* @param evaluator Selects from the available formats.
* @param representations The representations to be considered by the source.
*/
public
DashWebmChunkSource
(
DataSource
dataSource
,
FormatEvaluator
evaluator
,
Representation
...
representations
)
{
this
.
dataSource
=
dataSource
;
this
.
evaluator
=
evaluator
;
this
.
formats
=
new
Format
[
representations
.
length
];
this
.
extractors
=
new
HashMap
<
String
,
WebmExtractor
>();
this
.
extractors
=
new
HashMap
<
String
,
Default
WebmExtractor
>();
this
.
segmentIndexes
=
new
HashMap
<
String
,
DashSegmentIndex
>();
this
.
representations
=
new
HashMap
<
String
,
Representation
>();
this
.
trackInfo
=
new
TrackInfo
(
representations
[
0
].
format
.
mimeType
,
representations
[
0
].
periodDurationMs
*
1000
);
this
.
trackInfo
=
new
TrackInfo
(
representations
[
0
].
format
.
mimeType
,
representations
[
0
].
periodDurationMs
*
1000
);
this
.
evaluation
=
new
Evaluation
();
int
maxWidth
=
0
;
int
maxHeight
=
0
;
...
...
@@ -109,7 +114,7 @@ public class DashWebmChunkSource implements ChunkSource {
@Override
public
void
disable
(
List
<?
extends
MediaChunk
>
queue
)
{
evaluator
.
disable
();
evaluator
.
disable
();
}
@Override
...
...
@@ -140,13 +145,18 @@ public class DashWebmChunkSource implements ChunkSource {
Representation
selectedRepresentation
=
representations
.
get
(
selectedFormat
.
id
);
WebmExtractor
extractor
=
extractors
.
get
(
selectedRepresentation
.
format
.
id
);
if
(!
extractor
.
isPrepared
())
{
// TODO: This code forces cues to exist and to immediately follow the initialization
// 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
);
RangedUri
pendingInitializationUri
=
null
;
RangedUri
pendingIndexUri
=
null
;
if
(
extractor
.
getFormat
()
==
null
)
{
pendingInitializationUri
=
selectedRepresentation
.
getInitializationUri
();
}
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
;
out
.
chunk
=
initializationChunk
;
return
;
...
...
@@ -181,12 +191,29 @@ public class DashWebmChunkSource implements ChunkSource {
// Do nothing.
}
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
());
private
Chunk
newInitializationChunk
(
RangedUri
initializationUri
,
RangedUri
indexUri
,
Representation
representation
,
WebmExtractor
extractor
,
DataSource
dataSource
,
int
trigger
)
{
int
expectedExtractorResult
=
WebmExtractor
.
RESULT_END_OF_STREAM
;
RangedUri
requestUri
;
if
(
initializationUri
!=
null
)
{
// It's common for initialization and index data to be stored adjacently. Attempt to merge
// the two requests together to request both at once.
expectedExtractorResult
|=
WebmExtractor
.
RESULT_READ_INIT
;
requestUri
=
initializationUri
.
attemptMerge
(
indexUri
);
if
(
requestUri
!=
null
)
{
expectedExtractorResult
|=
WebmExtractor
.
RESULT_READ_INDEX
;
}
else
{
requestUri
=
initializationUri
;
}
}
else
{
requestUri
=
indexUri
;
expectedExtractorResult
|=
WebmExtractor
.
RESULT_READ_INDEX
;
}
DataSpec
dataSpec
=
new
DataSpec
(
requestUri
.
getUri
(),
requestUri
.
start
,
requestUri
.
length
,
representation
.
getCacheKey
());
return
new
InitializationWebmLoadable
(
dataSource
,
dataSpec
,
trigger
,
representation
.
format
,
extractor
);
extractor
,
expectedExtractorResult
);
}
private
Chunk
newMediaChunk
(
Representation
representation
,
DashSegmentIndex
segmentIndex
,
...
...
@@ -206,22 +233,27 @@ public class DashWebmChunkSource implements ChunkSource {
private
class
InitializationWebmLoadable
extends
Chunk
{
private
final
WebmExtractor
extractor
;
private
final
int
expectedExtractorResult
;
private
final
Uri
uri
;
public
InitializationWebmLoadable
(
DataSource
dataSource
,
DataSpec
dataSpec
,
int
trigger
,
Format
format
,
WebmExtractor
extractor
)
{
Format
format
,
WebmExtractor
extractor
,
int
expectedExtractorResult
)
{
super
(
dataSource
,
dataSpec
,
format
,
trigger
);
this
.
extractor
=
extractor
;
this
.
expectedExtractorResult
=
expectedExtractorResult
;
this
.
uri
=
dataSpec
.
uri
;
}
@Override
protected
void
consumeStream
(
NonBlockingInputStream
stream
)
throws
IOException
{
extractor
.
read
(
stream
,
null
);
if
(!
extractor
.
isPrepared
())
{
throw
new
ParserException
(
"Invalid initialization data"
);
int
result
=
extractor
.
read
(
stream
,
null
);
if
(
result
!=
expectedExtractorResult
)
{
throw
new
ParserException
(
"Invalid extractor result. Expected "
+
expectedExtractorResult
+
", got "
+
result
);
}
if
((
result
&
WebmExtractor
.
RESULT_READ_INDEX
)
!=
0
)
{
segmentIndexes
.
put
(
format
.
id
,
new
DashWrappingSegmentIndex
(
extractor
.
getIndex
(),
uri
,
0
));
}
segmentIndexes
.
put
(
format
.
id
,
new
DashWrappingSegmentIndex
(
extractor
.
getCues
(),
uri
,
0
));
}
}
...
...
library/src/main/java/com/google/android/exoplayer/dash/mpd/MediaPresentationDescriptionParser.java
View file @
79c2f535
...
...
@@ -140,6 +140,7 @@ public class MediaPresentationDescriptionParser extends DefaultHandler {
throws
XmlPullParserException
,
IOException
{
String
mimeType
=
xpp
.
getAttributeValue
(
null
,
"mimeType"
);
String
language
=
xpp
.
getAttributeValue
(
null
,
"lang"
);
int
contentType
=
parseAdaptationSetTypeFromMimeType
(
mimeType
);
int
id
=
-
1
;
...
...
@@ -160,7 +161,7 @@ public class MediaPresentationDescriptionParser extends DefaultHandler {
parseAdaptationSetType
(
xpp
.
getAttributeValue
(
null
,
"contentType"
)));
}
else
if
(
isStartTag
(
xpp
,
"Representation"
))
{
Representation
representation
=
parseRepresentation
(
xpp
,
contentId
,
baseUrl
,
periodStartMs
,
periodDurationMs
,
mimeType
,
segmentBase
);
periodDurationMs
,
mimeType
,
language
,
segmentBase
);
contentType
=
checkAdaptationSetTypeConsistency
(
contentType
,
parseAdaptationSetTypeFromMimeType
(
representation
.
format
.
mimeType
));
representations
.
add
(
representation
);
...
...
@@ -230,8 +231,8 @@ public class MediaPresentationDescriptionParser extends DefaultHandler {
// Representation parsing.
private
Representation
parseRepresentation
(
XmlPullParser
xpp
,
String
contentId
,
Uri
baseUrl
,
long
periodStartMs
,
long
periodDurationMs
,
String
mimeType
,
S
egmentBase
segmentBase
)
throws
XmlPullParserException
,
IOException
{
long
periodStartMs
,
long
periodDurationMs
,
String
mimeType
,
S
tring
language
,
SegmentBase
segmentBase
)
throws
XmlPullParserException
,
IOException
{
String
id
=
xpp
.
getAttributeValue
(
null
,
"id"
);
int
bandwidth
=
parseInt
(
xpp
,
"bandwidth"
);
int
audioSamplingRate
=
parseInt
(
xpp
,
"audioSamplingRate"
);
...
...
@@ -257,7 +258,7 @@ public class MediaPresentationDescriptionParser extends DefaultHandler {
}
while
(!
isEndTag
(
xpp
,
"Representation"
));
Format
format
=
new
Format
(
id
,
mimeType
,
width
,
height
,
numChannels
,
audioSamplingRate
,
bandwidth
);
bandwidth
,
language
);
return
Representation
.
newInstance
(
periodStartMs
,
periodDurationMs
,
contentId
,
-
1
,
format
,
segmentBase
);
}
...
...
library/src/main/java/com/google/android/exoplayer/parser/mp4/Atom.java
View file @
79c2f535
...
...
@@ -16,7 +16,6 @@
package
com
.
google
.
android
.
exoplayer
.
parser
.
mp4
;
import
java.util.ArrayList
;
import
java.util.List
;
/* package */
abstract
class
Atom
{
...
...
@@ -24,7 +23,6 @@ import java.util.List;
public
static
final
int
TYPE_avc3
=
0x61766333
;
public
static
final
int
TYPE_esds
=
0x65736473
;
public
static
final
int
TYPE_mdat
=
0x6D646174
;
public
static
final
int
TYPE_mfhd
=
0x6D666864
;
public
static
final
int
TYPE_mp4a
=
0x6D703461
;
public
static
final
int
TYPE_tfdt
=
0x74666474
;
public
static
final
int
TYPE_tfhd
=
0x74666864
;
...
...
@@ -54,6 +52,7 @@ import java.util.List;
public
static
final
int
TYPE_frma
=
0x66726D61
;
public
static
final
int
TYPE_saiz
=
0x7361697A
;
public
static
final
int
TYPE_uuid
=
0x75756964
;
public
static
final
int
TYPE_senc
=
0x73656E63
;
public
final
int
type
;
...
...
@@ -63,17 +62,13 @@ import java.util.List;
public
final
static
class
LeafAtom
extends
Atom
{
p
rivate
final
ParsableByteArray
data
;
p
ublic
final
ParsableByteArray
data
;
public
LeafAtom
(
int
type
,
ParsableByteArray
data
)
{
super
(
type
);
this
.
data
=
data
;
}
public
ParsableByteArray
getData
()
{
return
data
;
}
}
public
final
static
class
ContainerAtom
extends
Atom
{
...
...
@@ -90,7 +85,8 @@ import java.util.List;
}
public
LeafAtom
getLeafAtomOfType
(
int
type
)
{
for
(
int
i
=
0
;
i
<
children
.
size
();
i
++)
{
int
childrenSize
=
children
.
size
();
for
(
int
i
=
0
;
i
<
childrenSize
;
i
++)
{
Atom
atom
=
children
.
get
(
i
);
if
(
atom
.
type
==
type
)
{
return
(
LeafAtom
)
atom
;
...
...
@@ -100,7 +96,8 @@ import java.util.List;
}
public
ContainerAtom
getContainerAtomOfType
(
int
type
)
{
for
(
int
i
=
0
;
i
<
children
.
size
();
i
++)
{
int
childrenSize
=
children
.
size
();
for
(
int
i
=
0
;
i
<
childrenSize
;
i
++)
{
Atom
atom
=
children
.
get
(
i
);
if
(
atom
.
type
==
type
)
{
return
(
ContainerAtom
)
atom
;
...
...
@@ -109,10 +106,6 @@ import java.util.List;
return
null
;
}
public
List
<
Atom
>
getChildren
()
{
return
children
;
}
}
}
library/src/main/java/com/google/android/exoplayer/parser/mp4/CodecSpecificDataUtil.java
View file @
79c2f535
...
...
@@ -27,7 +27,7 @@ import java.util.List;
/**
* Provides static utility methods for manipulating various types of codec specific data.
*/
public
class
CodecSpecificDataUtil
{
public
final
class
CodecSpecificDataUtil
{
private
static
final
byte
[]
NAL_START_CODE
=
new
byte
[]
{
0
,
0
,
0
,
1
};
...
...
library/src/main/java/com/google/android/exoplayer/parser/mp4/FragmentedMp4Extractor.java
View file @
79c2f535
...
...
@@ -59,7 +59,7 @@ public final class FragmentedMp4Extractor {
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
insufficient
data.
*/
public
static
final
int
RESULT_NEED_MORE_DATA
=
1
;
/**
...
...
@@ -69,27 +69,23 @@ public final class FragmentedMp4Extractor {
/**
* A media sample was read.
*/
public
static
final
int
RESULT_READ_SAMPLE
_FULL
=
4
;
public
static
final
int
RESULT_READ_SAMPLE
=
4
;
/**
* A media sample was partially read.
* A moov atom was read. The parsed data can be read using {@link #getFormat()} and
* {@link #getPsshInfo}.
*/
public
static
final
int
RESULT_READ_
SAMPLE_PARTIAL
=
8
;
public
static
final
int
RESULT_READ_
INIT
=
8
;
/**
* A moov atom was read. The parsed data can be read using {@link #getTrack()},
* {@link #getFormat()} and {@link #getPsshInfo}.
* A sidx atom was read. The parsed data can be read using {@link #getIndex()}.
*/
public
static
final
int
RESULT_READ_MOOV
=
16
;
/**
* 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_INDEX
=
16
;
/**
* 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
;
public
static
final
int
RESULT_NEED_SAMPLE_HOLDER
=
32
;
private
static
final
int
READ_TERMINATING_RESULTS
=
RESULT_NEED_MORE_DATA
|
RESULT_END_OF_STREAM
|
RESULT_READ_SAMPLE
_FULL
|
RESULT_NEED_SAMPLE_HOLDER
;
|
RESULT_READ_SAMPLE
|
RESULT_NEED_SAMPLE_HOLDER
;
private
static
final
byte
[]
NAL_START_CODE
=
new
byte
[]
{
0
,
0
,
0
,
1
};
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
};
...
...
@@ -97,9 +93,8 @@ public final class FragmentedMp4Extractor {
// Parser states
private
static
final
int
STATE_READING_ATOM_HEADER
=
0
;
private
static
final
int
STATE_READING_ATOM_PAYLOAD
=
1
;
private
static
final
int
STATE_READING_CENC_AUXILIARY_DATA
=
2
;
private
static
final
int
STATE_READING_SAMPLE_START
=
3
;
private
static
final
int
STATE_READING_SAMPLE_INCREMENTAL
=
4
;
private
static
final
int
STATE_READING_ENCRYPTION_DATA
=
2
;
private
static
final
int
STATE_READING_SAMPLE
=
3
;
// Atom data offsets
private
static
final
int
ATOM_HEADER_SIZE
=
8
;
...
...
@@ -115,7 +110,6 @@ public final class FragmentedMp4Extractor {
parsedAtoms
.
add
(
Atom
.
TYPE_hdlr
);
parsedAtoms
.
add
(
Atom
.
TYPE_mdat
);
parsedAtoms
.
add
(
Atom
.
TYPE_mdhd
);
parsedAtoms
.
add
(
Atom
.
TYPE_mfhd
);
parsedAtoms
.
add
(
Atom
.
TYPE_moof
);
parsedAtoms
.
add
(
Atom
.
TYPE_moov
);
parsedAtoms
.
add
(
Atom
.
TYPE_mp4a
);
...
...
@@ -135,6 +129,7 @@ public final class FragmentedMp4Extractor {
parsedAtoms
.
add
(
Atom
.
TYPE_pssh
);
parsedAtoms
.
add
(
Atom
.
TYPE_saiz
);
parsedAtoms
.
add
(
Atom
.
TYPE_uuid
);
parsedAtoms
.
add
(
Atom
.
TYPE_senc
);
PARSED_ATOMS
=
Collections
.
unmodifiableSet
(
parsedAtoms
);
}
...
...
@@ -158,8 +153,10 @@ public final class FragmentedMp4Extractor {
// Parser state
private
final
ParsableByteArray
atomHeader
;
private
final
byte
[]
extendedTypeScratch
;
private
final
Stack
<
ContainerAtom
>
containerAtoms
;
private
final
Stack
<
Integer
>
containerAtomEndPoints
;
private
final
TrackFragment
fragmentRun
;
private
int
parserState
;
private
int
atomBytesRead
;
...
...
@@ -167,9 +164,6 @@ public final class FragmentedMp4Extractor {
private
int
atomType
;
private
int
atomSize
;
private
ParsableByteArray
atomData
;
private
ParsableByteArray
cencAuxiliaryData
;
private
int
cencAuxiliaryBytesRead
;
private
int
sampleBytesRead
;
private
int
pendingSeekTimeMs
;
private
int
sampleIndex
;
...
...
@@ -182,9 +176,6 @@ public final class FragmentedMp4Extractor {
private
Track
track
;
private
DefaultSampleValues
extendsDefaults
;
// Data parsed from the most recent moof atom
private
TrackFragment
fragmentRun
;
public
FragmentedMp4Extractor
()
{
this
(
0
);
}
...
...
@@ -197,8 +188,10 @@ public final class FragmentedMp4Extractor {
this
.
workaroundFlags
=
workaroundFlags
;
parserState
=
STATE_READING_ATOM_HEADER
;
atomHeader
=
new
ParsableByteArray
(
ATOM_HEADER_SIZE
);
extendedTypeScratch
=
new
byte
[
16
];
containerAtoms
=
new
Stack
<
ContainerAtom
>();
containerAtomEndPoints
=
new
Stack
<
Integer
>();
fragmentRun
=
new
TrackFragment
();
psshData
=
new
HashMap
<
UUID
,
byte
[]>();
}
...
...
@@ -207,7 +200,7 @@ public final class FragmentedMp4Extractor {
*
* @return The segment index, or null if a SIDX atom has yet to be parsed.
*/
public
SegmentIndex
get
Segment
Index
()
{
public
SegmentIndex
getIndex
()
{
return
segmentIndex
;
}
...
...
@@ -245,17 +238,7 @@ public final class FragmentedMp4Extractor {
}
/**
* Returns the track information parsed from the stream.
*
* @return The track, or null if a MOOV atom has yet to be parsed.
*/
public
Track
getTrack
()
{
return
track
;
}
/**
* Sideloads track information into the extractor, so that it can be read through
* {@link #getTrack()}.
* Sideloads track information into the extractor.
*
* @param track The track to sideload.
*/
...
...
@@ -270,10 +253,6 @@ public final class FragmentedMp4Extractor {
* The read terminates if the end of the input stream is reached, if an attempt to read from the
* input stream returned 0 bytes of data, or if a sample is read. The returned flags indicate
* both the reason for termination and data that was parsed during the read.
* <p>
* If the returned flags include {@link #RESULT_READ_SAMPLE_PARTIAL} then the sample has been
* partially read into {@code out}. Hence the same {@link SampleHolder} instance must be passed
* in subsequent calls until the whole sample has been read.
*
* @param inputStream The input stream from which data should be read.
* @param out A {@link SampleHolder} into which the next sample should be read. If null then
...
...
@@ -293,8 +272,8 @@ public final class FragmentedMp4Extractor {
case
STATE_READING_ATOM_PAYLOAD:
results
|=
readAtomPayload
(
inputStream
);
break
;
case
STATE_READING_
CENC_AUXILIARY
_DATA:
results
|=
read
CencAuxiliary
Data
(
inputStream
);
case
STATE_READING_
ENCRYPTION
_DATA:
results
|=
read
Encryption
Data
(
inputStream
);
break
;
default
:
results
|=
readOrSkipSample
(
inputStream
,
out
);
...
...
@@ -350,19 +329,13 @@ public final class FragmentedMp4Extractor {
rootAtomBytesRead
=
0
;
}
break
;
case
STATE_READING_CENC_AUXILIARY_DATA:
cencAuxiliaryBytesRead
=
0
;
break
;
case
STATE_READING_SAMPLE_START:
sampleBytesRead
=
0
;
break
;
}
parserState
=
state
;
}
private
int
readAtomHeader
(
NonBlockingInputStream
inputStream
)
{
int
remainingBytes
=
ATOM_HEADER_SIZE
-
atomBytesRead
;
int
bytesRead
=
inputStream
.
read
(
atomHeader
.
getData
()
,
atomBytesRead
,
remainingBytes
);
int
bytesRead
=
inputStream
.
read
(
atomHeader
.
data
,
atomBytesRead
,
remainingBytes
);
if
(
bytesRead
==
-
1
)
{
return
RESULT_END_OF_STREAM
;
}
...
...
@@ -377,13 +350,10 @@ public final class FragmentedMp4Extractor {
atomType
=
atomHeader
.
readInt
();
if
(
atomType
==
Atom
.
TYPE_mdat
)
{
int
cencAuxSize
=
fragmentRun
.
auxiliarySampleInfoTotalSize
;
if
(
cencAuxSize
>
0
)
{
cencAuxiliaryData
=
new
ParsableByteArray
(
cencAuxSize
);
enterState
(
STATE_READING_CENC_AUXILIARY_DATA
);
if
(
fragmentRun
.
sampleEncryptionDataNeedsFill
)
{
enterState
(
STATE_READING_ENCRYPTION_DATA
);
}
else
{
cencAuxiliaryData
=
null
;
enterState
(
STATE_READING_SAMPLE_START
);
enterState
(
STATE_READING_SAMPLE
);
}
return
0
;
}
...
...
@@ -395,7 +365,7 @@ public final class FragmentedMp4Extractor {
containerAtomEndPoints
.
add
(
rootAtomBytesRead
+
atomSize
-
ATOM_HEADER_SIZE
);
}
else
{
atomData
=
new
ParsableByteArray
(
atomSize
);
System
.
arraycopy
(
atomHeader
.
getData
(),
0
,
atomData
.
getData
()
,
0
,
ATOM_HEADER_SIZE
);
System
.
arraycopy
(
atomHeader
.
data
,
0
,
atomData
.
data
,
0
,
ATOM_HEADER_SIZE
);
enterState
(
STATE_READING_ATOM_PAYLOAD
);
}
}
else
{
...
...
@@ -409,7 +379,7 @@ public final class FragmentedMp4Extractor {
private
int
readAtomPayload
(
NonBlockingInputStream
inputStream
)
{
int
bytesRead
;
if
(
atomData
!=
null
)
{
bytesRead
=
inputStream
.
read
(
atomData
.
getData
()
,
atomBytesRead
,
atomSize
-
atomBytesRead
);
bytesRead
=
inputStream
.
read
(
atomData
.
data
,
atomBytesRead
,
atomSize
-
atomBytesRead
);
}
else
{
bytesRead
=
inputStream
.
skip
(
atomSize
-
atomBytesRead
);
}
...
...
@@ -441,8 +411,8 @@ public final class FragmentedMp4Extractor {
if
(!
containerAtoms
.
isEmpty
())
{
containerAtoms
.
peek
().
add
(
leaf
);
}
else
if
(
leaf
.
type
==
Atom
.
TYPE_sidx
)
{
segmentIndex
=
parseSidx
(
leaf
.
getData
()
);
return
RESULT_READ_
SID
X
;
segmentIndex
=
parseSidx
(
leaf
.
data
);
return
RESULT_READ_
INDE
X
;
}
return
0
;
}
...
...
@@ -450,7 +420,7 @@ public final class FragmentedMp4Extractor {
private
int
onContainerAtomRead
(
ContainerAtom
container
)
{
if
(
container
.
type
==
Atom
.
TYPE_moov
)
{
onMoovContainerAtomRead
(
container
);
return
RESULT_READ_
MOOV
;
return
RESULT_READ_
INIT
;
}
else
if
(
container
.
type
==
Atom
.
TYPE_moof
)
{
onMoofContainerAtomRead
(
container
);
}
else
if
(!
containerAtoms
.
isEmpty
())
{
...
...
@@ -460,11 +430,12 @@ public final class FragmentedMp4Extractor {
}
private
void
onMoovContainerAtomRead
(
ContainerAtom
moov
)
{
List
<
Atom
>
moovChildren
=
moov
.
getChildren
();
for
(
int
i
=
0
;
i
<
moovChildren
.
size
();
i
++)
{
List
<
Atom
>
moovChildren
=
moov
.
children
;
int
moovChildrenSize
=
moovChildren
.
size
();
for
(
int
i
=
0
;
i
<
moovChildrenSize
;
i
++)
{
Atom
child
=
moovChildren
.
get
(
i
);
if
(
child
.
type
==
Atom
.
TYPE_pssh
)
{
ParsableByteArray
psshAtom
=
((
LeafAtom
)
child
).
getData
()
;
ParsableByteArray
psshAtom
=
((
LeafAtom
)
child
).
data
;
psshAtom
.
setPosition
(
FULL_ATOM_HEADER_SIZE
);
UUID
uuid
=
new
UUID
(
psshAtom
.
readLong
(),
psshAtom
.
readLong
());
int
dataSize
=
psshAtom
.
readInt
();
...
...
@@ -474,13 +445,13 @@ public final class FragmentedMp4Extractor {
}
}
ContainerAtom
mvex
=
moov
.
getContainerAtomOfType
(
Atom
.
TYPE_mvex
);
extendsDefaults
=
parseTrex
(
mvex
.
getLeafAtomOfType
(
Atom
.
TYPE_trex
).
getData
()
);
extendsDefaults
=
parseTrex
(
mvex
.
getLeafAtomOfType
(
Atom
.
TYPE_trex
).
data
);
track
=
parseTrak
(
moov
.
getContainerAtomOfType
(
Atom
.
TYPE_trak
));
}
private
void
onMoofContainerAtomRead
(
ContainerAtom
moof
)
{
fragmentRun
=
new
TrackFragmen
t
();
parseMoof
(
track
,
extendsDefaults
,
moof
,
fragmentRun
,
workaroundFlags
);
fragmentRun
.
rese
t
();
parseMoof
(
track
,
extendsDefaults
,
moof
,
fragmentRun
,
workaroundFlags
,
extendedTypeScratch
);
sampleIndex
=
0
;
lastSyncSampleIndex
=
0
;
pendingSeekSyncSampleIndex
=
0
;
...
...
@@ -514,21 +485,21 @@ public final class FragmentedMp4Extractor {
*/
private
static
Track
parseTrak
(
ContainerAtom
trak
)
{
ContainerAtom
mdia
=
trak
.
getContainerAtomOfType
(
Atom
.
TYPE_mdia
);
int
trackType
=
parseHdlr
(
mdia
.
getLeafAtomOfType
(
Atom
.
TYPE_hdlr
).
getData
()
);
int
trackType
=
parseHdlr
(
mdia
.
getLeafAtomOfType
(
Atom
.
TYPE_hdlr
).
data
);
Assertions
.
checkState
(
trackType
==
Track
.
TYPE_AUDIO
||
trackType
==
Track
.
TYPE_VIDEO
);
Pair
<
Integer
,
Long
>
header
=
parseTkhd
(
trak
.
getLeafAtomOfType
(
Atom
.
TYPE_tkhd
).
getData
()
);
Pair
<
Integer
,
Long
>
header
=
parseTkhd
(
trak
.
getLeafAtomOfType
(
Atom
.
TYPE_tkhd
).
data
);
int
id
=
header
.
first
;
// TODO: This value should be used to set a duration field on the Track object
// instantiated below, however we've found examples where the value is 0. Revisit whether we
// should set it anyway (and just have it be wrong for bad media streams).
// long duration = header.second;
long
timescale
=
parseMdhd
(
mdia
.
getLeafAtomOfType
(
Atom
.
TYPE_mdhd
).
getData
()
);
long
timescale
=
parseMdhd
(
mdia
.
getLeafAtomOfType
(
Atom
.
TYPE_mdhd
).
data
);
ContainerAtom
stbl
=
mdia
.
getContainerAtomOfType
(
Atom
.
TYPE_minf
)
.
getContainerAtomOfType
(
Atom
.
TYPE_stbl
);
Pair
<
MediaFormat
,
TrackEncryptionBox
[]>
sampleDescriptions
=
parseStsd
(
stbl
.
getLeafAtomOfType
(
Atom
.
TYPE_stsd
).
getData
()
);
parseStsd
(
stbl
.
getLeafAtomOfType
(
Atom
.
TYPE_stsd
).
data
);
return
new
Track
(
id
,
trackType
,
timescale
,
sampleDescriptions
.
first
,
sampleDescriptions
.
second
);
}
...
...
@@ -654,7 +625,7 @@ public final class FragmentedMp4Extractor {
if
(
childAtomType
==
Atom
.
TYPE_esds
)
{
initializationData
=
parseEsdsFromParent
(
parent
,
childStartPosition
);
// TODO: Do we really need to do this? See [redacted]
// Update sampleRate and
sampleRate
from the AudioSpecificConfig initialization data.
// Update sampleRate and
channelCount
from the AudioSpecificConfig initialization data.
Pair
<
Integer
,
Integer
>
audioSpecificConfig
=
CodecSpecificDataUtil
.
parseAudioSpecificConfig
(
initializationData
);
sampleRate
=
audioSpecificConfig
.
first
;
...
...
@@ -697,7 +668,7 @@ public final class FragmentedMp4Extractor {
int
length
=
atom
.
readUnsignedShort
();
int
offset
=
atom
.
getPosition
();
atom
.
skip
(
length
);
return
CodecSpecificDataUtil
.
buildNalUnit
(
atom
.
getData
()
,
offset
,
length
);
return
CodecSpecificDataUtil
.
buildNalUnit
(
atom
.
data
,
offset
,
length
);
}
private
static
TrackEncryptionBox
parseSinfFromParent
(
ParsableByteArray
parent
,
int
position
,
...
...
@@ -775,7 +746,7 @@ public final class FragmentedMp4Extractor {
parent
.
skip
(
13
);
// Start of AudioSpecificConfig (defined in 14496-3)
parent
.
skip
(
1
);
// AudioSpecificConfig tag
parent
.
skip
(
1
);
// AudioSpecificConfig tag
varIntByte
=
parent
.
readUnsignedByte
();
int
varInt
=
varIntByte
&
0x7F
;
while
(
varIntByte
>
127
)
{
...
...
@@ -789,49 +760,47 @@ public final class FragmentedMp4Extractor {
}
private
static
void
parseMoof
(
Track
track
,
DefaultSampleValues
extendsDefaults
,
ContainerAtom
moof
,
TrackFragment
out
,
int
workaroundFlags
)
{
// TODO: Consider checking that the sequence number returned by parseMfhd is as expected.
parseMfhd
(
moof
.
getLeafAtomOfType
(
Atom
.
TYPE_mfhd
).
getData
());
ContainerAtom
moof
,
TrackFragment
out
,
int
workaroundFlags
,
byte
[]
extendedTypeScratch
)
{
parseTraf
(
track
,
extendsDefaults
,
moof
.
getContainerAtomOfType
(
Atom
.
TYPE_traf
),
out
,
workaroundFlags
);
}
/**
* Parses an mfhd atom (defined in 14496-12).
*
* @param mfhd The mfhd atom to parse.
* @return The sequence number of the fragment.
*/
private
static
int
parseMfhd
(
ParsableByteArray
mfhd
)
{
mfhd
.
setPosition
(
FULL_ATOM_HEADER_SIZE
);
return
mfhd
.
readUnsignedIntToInt
();
out
,
workaroundFlags
,
extendedTypeScratch
);
}
/**
* Parses a traf atom (defined in 14496-12).
*/
private
static
void
parseTraf
(
Track
track
,
DefaultSampleValues
extendsDefaults
,
ContainerAtom
traf
,
TrackFragment
out
,
int
workaroundFlags
)
{
LeafAtom
saiz
=
traf
.
getLeafAtomOfType
(
Atom
.
TYPE_saiz
);
if
(
saiz
!=
null
)
{
parseSaiz
(
saiz
.
getData
(),
out
);
}
ContainerAtom
traf
,
TrackFragment
out
,
int
workaroundFlags
,
byte
[]
extendedTypeScratch
)
{
LeafAtom
tfdtAtom
=
traf
.
getLeafAtomOfType
(
Atom
.
TYPE_tfdt
);
long
decodeTime
=
tfdtAtom
==
null
?
0
:
parseTfdt
(
traf
.
getLeafAtomOfType
(
Atom
.
TYPE_tfdt
).
getData
());
long
decodeTime
=
tfdtAtom
==
null
?
0
:
parseTfdt
(
traf
.
getLeafAtomOfType
(
Atom
.
TYPE_tfdt
).
data
);
LeafAtom
tfhd
=
traf
.
getLeafAtomOfType
(
Atom
.
TYPE_tfhd
);
DefaultSampleValues
fragmentHeader
=
parseTfhd
(
extendsDefaults
,
tfhd
.
getData
()
);
out
.
s
etSampleDescriptionIndex
(
fragmentHeader
.
sampleDescriptionIndex
)
;
DefaultSampleValues
fragmentHeader
=
parseTfhd
(
extendsDefaults
,
tfhd
.
data
);
out
.
s
ampleDescriptionIndex
=
fragmentHeader
.
sampleDescriptionIndex
;
LeafAtom
trun
=
traf
.
getLeafAtomOfType
(
Atom
.
TYPE_trun
);
parseTrun
(
track
,
fragmentHeader
,
decodeTime
,
workaroundFlags
,
trun
.
getData
(),
out
);
parseTrun
(
track
,
fragmentHeader
,
decodeTime
,
workaroundFlags
,
trun
.
data
,
out
);
TrackEncryptionBox
trackEncryptionBox
=
track
.
sampleDescriptionEncryptionBoxes
[
fragmentHeader
.
sampleDescriptionIndex
];
LeafAtom
saiz
=
traf
.
getLeafAtomOfType
(
Atom
.
TYPE_saiz
);
if
(
saiz
!=
null
)
{
parseSaiz
(
trackEncryptionBox
,
saiz
.
data
,
out
);
}
LeafAtom
senc
=
traf
.
getLeafAtomOfType
(
Atom
.
TYPE_senc
);
if
(
senc
!=
null
)
{
parseSenc
(
senc
.
data
,
out
);
}
LeafAtom
uuid
=
traf
.
getLeafAtomOfType
(
Atom
.
TYPE_uuid
);
if
(
uuid
!=
null
)
{
parseUuid
(
uuid
.
getData
(),
out
);
parseUuid
(
uuid
.
data
,
out
,
extendedTypeScratch
);
}
}
private
static
void
parseSaiz
(
ParsableByteArray
saiz
,
TrackFragment
out
)
{
private
static
void
parseSaiz
(
TrackEncryptionBox
encryptionBox
,
ParsableByteArray
saiz
,
TrackFragment
out
)
{
int
vectorSize
=
encryptionBox
.
initializationVectorSize
;
saiz
.
setPosition
(
ATOM_HEADER_SIZE
);
int
fullAtom
=
saiz
.
readInt
();
int
flags
=
parseFullAtomFlags
(
fullAtom
);
...
...
@@ -839,21 +808,26 @@ public final class FragmentedMp4Extractor {
saiz
.
skip
(
8
);
}
int
defaultSampleInfoSize
=
saiz
.
readUnsignedByte
();
int
sampleCount
=
saiz
.
readUnsignedIntToInt
();
if
(
sampleCount
!=
out
.
length
)
{
throw
new
IllegalStateException
(
"Length mismatch: "
+
sampleCount
+
", "
+
out
.
length
);
}
int
totalSize
=
0
;
int
[]
sampleInfoSizes
=
new
int
[
sampleCount
];
if
(
defaultSampleInfoSize
==
0
)
{
boolean
[]
sampleHasSubsampleEncryptionTable
=
out
.
sampleHasSubsampleEncryptionTable
;
for
(
int
i
=
0
;
i
<
sampleCount
;
i
++)
{
sampleInfoSizes
[
i
]
=
saiz
.
readUnsignedByte
();
totalSize
+=
sampleInfoSizes
[
i
];
int
sampleInfoSize
=
saiz
.
readUnsignedByte
();
totalSize
+=
sampleInfoSize
;
sampleHasSubsampleEncryptionTable
[
i
]
=
sampleInfoSize
>
vectorSize
;
}
}
else
{
for
(
int
i
=
0
;
i
<
sampleCount
;
i
++)
{
sampleInfoSizes
[
i
]
=
defaultSampleInfoSize
;
totalSize
+=
defaultSampleInfoSize
;
}
boolean
subsampleEncryption
=
defaultSampleInfoSize
>
vectorSize
;
totalSize
+=
defaultSampleInfoSize
*
sampleCount
;
Arrays
.
fill
(
out
.
sampleHasSubsampleEncryptionTable
,
0
,
sampleCount
,
subsampleEncryption
);
}
out
.
setAuxiliarySampleInfoTables
(
totalSize
,
sampleInfoSizes
);
out
.
initEncryptionData
(
totalSize
);
}
/**
...
...
@@ -912,10 +886,9 @@ public final class FragmentedMp4Extractor {
long
decodeTime
,
int
workaroundFlags
,
ParsableByteArray
trun
,
TrackFragment
out
)
{
trun
.
setPosition
(
ATOM_HEADER_SIZE
);
int
fullAtom
=
trun
.
readInt
();
int
version
=
parseFullAtomVersion
(
fullAtom
);
int
flags
=
parseFullAtomFlags
(
fullAtom
);
int
numberOfEntries
=
trun
.
readUnsignedIntToInt
();
int
sampleCount
=
trun
.
readUnsignedIntToInt
();
if
((
flags
&
0x01
/* data_offset_present */
)
!=
0
)
{
trun
.
skip
(
4
);
}
...
...
@@ -932,17 +905,18 @@ public final class FragmentedMp4Extractor {
boolean
sampleCompositionTimeOffsetsPresent
=
(
flags
&
0x800
/* sample_composition_time_offsets_present */
)
!=
0
;
int
[]
sampleSizeTable
=
new
int
[
numberOfEntries
];
int
[]
sampleDecodingTimeTable
=
new
int
[
numberOfEntries
];
int
[]
sampleCompositionTimeOffsetTable
=
new
int
[
numberOfEntries
];
boolean
[]
sampleIsSyncFrameTable
=
new
boolean
[
numberOfEntries
];
out
.
initTables
(
sampleCount
);
int
[]
sampleSizeTable
=
out
.
sampleSizeTable
;
int
[]
sampleDecodingTimeTable
=
out
.
sampleDecodingTimeTable
;
int
[]
sampleCompositionTimeOffsetTable
=
out
.
sampleCompositionTimeOffsetTable
;
boolean
[]
sampleIsSyncFrameTable
=
out
.
sampleIsSyncFrameTable
;
long
timescale
=
track
.
timescale
;
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
<
sampleCount
;
i
++)
{
// Use trun values if present, otherwise tfhd, otherwise trex.
int
sampleDuration
=
sampleDurationsPresent
?
trun
.
readUnsignedIntToInt
()
:
defaultSampleValues
.
duration
;
...
...
@@ -950,48 +924,47 @@ public final class FragmentedMp4Extractor {
int
sampleFlags
=
(
i
==
0
&&
firstSampleFlagsPresent
)
?
firstSampleFlags
:
sampleFlagsPresent
?
trun
.
readInt
()
:
defaultSampleValues
.
flags
;
if
(
sampleCompositionTimeOffsetsPresent
)
{
int
sampleOffset
;
if
(
version
==
0
)
{
// 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
{
sampleOffset
=
trun
.
readInt
();
}
// 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).
int
sampleOffset
=
trun
.
readInt
();
sampleCompositionTimeOffsetTable
[
i
]
=
(
int
)
((
sampleOffset
*
1000
)
/
timescale
);
}
else
{
sampleCompositionTimeOffsetTable
[
i
]
=
0
;
}
sampleDecodingTimeTable
[
i
]
=
(
int
)
((
cumulativeTime
*
1000
)
/
timescale
);
sampleSizeTable
[
i
]
=
sampleSize
;
boolean
isSync
=
((
sampleFlags
>>
16
)
&
0x1
)
==
0
;
if
(
workaroundEveryVideoFrameIsSyncFrame
&&
i
!=
0
)
{
isSync
=
false
;
}
if
(
isSync
)
{
sampleIsSyncFrameTable
[
i
]
=
true
;
}
sampleIsSyncFrameTable
[
i
]
=
((
sampleFlags
>>
16
)
&
0x1
)
==
0
&&
(!
workaroundEveryVideoFrameIsSyncFrame
||
i
==
0
);
cumulativeTime
+=
sampleDuration
;
}
out
.
setSampleTables
(
sampleSizeTable
,
sampleDecodingTimeTable
,
sampleCompositionTimeOffsetTable
,
sampleIsSyncFrameTable
);
}
private
static
void
parseUuid
(
ParsableByteArray
uuid
,
TrackFragment
out
)
{
private
static
void
parseUuid
(
ParsableByteArray
uuid
,
TrackFragment
out
,
byte
[]
extendedTypeScratch
)
{
uuid
.
setPosition
(
ATOM_HEADER_SIZE
);
byte
[]
extendedType
=
new
byte
[
16
];
uuid
.
readBytes
(
extendedType
,
0
,
16
);
uuid
.
readBytes
(
extendedTypeScratch
,
0
,
16
);
// Currently this parser only supports Microsoft's PIFF SampleEncryptionBox.
if
(!
Arrays
.
equals
(
extendedType
,
PIFF_SAMPLE_ENCRYPTION_BOX_EXTENDED_TYPE
))
{
if
(!
Arrays
.
equals
(
extendedType
Scratch
,
PIFF_SAMPLE_ENCRYPTION_BOX_EXTENDED_TYPE
))
{
return
;
}
// See "Portable encoding of audio-video objects: The Protected Interoperable File Format
// (PIFF), John A. Bocharov et al, Section 5.3.2.1."
int
fullAtom
=
uuid
.
readInt
();
// Except for the extended type, this box is identical to a SENC box. See "Portable encoding of
// audio-video objects: The Protected Interoperable File Format (PIFF), John A. Bocharov et al,
// Section 5.3.2.1."
parseSenc
(
uuid
,
16
,
out
);
}
private
static
void
parseSenc
(
ParsableByteArray
senc
,
TrackFragment
out
)
{
parseSenc
(
senc
,
0
,
out
);
}
private
static
void
parseSenc
(
ParsableByteArray
senc
,
int
offset
,
TrackFragment
out
)
{
senc
.
setPosition
(
ATOM_HEADER_SIZE
+
offset
);
int
fullAtom
=
senc
.
readInt
();
int
flags
=
parseFullAtomFlags
(
fullAtom
);
if
((
flags
&
0x01
/* override_track_encryption_box_parameters */
)
!=
0
)
{
...
...
@@ -1000,15 +973,14 @@ public final class FragmentedMp4Extractor {
}
boolean
subsampleEncryption
=
(
flags
&
0x02
/* use_subsample_encryption */
)
!=
0
;
int
numberOfEntries
=
uuid
.
readUnsignedIntToInt
();
if
(
numberOfEntries
!=
out
.
length
)
{
throw
new
IllegalStateException
(
"Length mismatch: "
+
numberOfEntries
+
", "
+
out
.
length
);
int
sampleCount
=
senc
.
readUnsignedIntToInt
();
if
(
sampleCount
!=
out
.
length
)
{
throw
new
IllegalStateException
(
"Length mismatch: "
+
sampleCount
+
", "
+
out
.
length
);
}
int
sampleEncryptionDataLength
=
uuid
.
length
()
-
uuid
.
getPosition
();
ParsableByteArray
sampleEncryptionData
=
new
ParsableByteArray
(
sampleEncryptionDataLength
);
uuid
.
readBytes
(
sampleEncryptionData
.
getData
(),
0
,
sampleEncryptionData
.
length
());
out
.
setSmoothStreamingSampleEncryptionData
(
sampleEncryptionData
,
subsampleEncryption
);
Arrays
.
fill
(
out
.
sampleHasSubsampleEncryptionTable
,
0
,
sampleCount
,
subsampleEncryption
);
out
.
initEncryptionData
(
senc
.
length
()
-
senc
.
getPosition
());
out
.
fillEncryptionData
(
senc
);
}
/**
...
...
@@ -1067,18 +1039,12 @@ public final class FragmentedMp4Extractor {
return
new
SegmentIndex
(
atom
.
length
(),
sizes
,
offsets
,
durationsUs
,
timesUs
);
}
private
int
readCencAuxiliaryData
(
NonBlockingInputStream
inputStream
)
{
int
length
=
cencAuxiliaryData
.
length
();
int
bytesRead
=
inputStream
.
read
(
cencAuxiliaryData
.
getData
(),
cencAuxiliaryBytesRead
,
length
-
cencAuxiliaryBytesRead
);
if
(
bytesRead
==
-
1
)
{
return
RESULT_END_OF_STREAM
;
}
cencAuxiliaryBytesRead
+=
bytesRead
;
if
(
cencAuxiliaryBytesRead
<
length
)
{
private
int
readEncryptionData
(
NonBlockingInputStream
inputStream
)
{
boolean
success
=
fragmentRun
.
fillEncryptionData
(
inputStream
);
if
(!
success
)
{
return
RESULT_NEED_MORE_DATA
;
}
enterState
(
STATE_READING_SAMPLE
_START
);
enterState
(
STATE_READING_SAMPLE
);
return
0
;
}
...
...
@@ -1105,89 +1071,62 @@ public final class FragmentedMp4Extractor {
enterState
(
STATE_READING_ATOM_HEADER
);
return
0
;
}
int
sampleSize
=
fragmentRun
.
sampleSizeTable
[
sampleIndex
];
if
(
inputStream
.
getAvailableByteCount
()
<
sampleSize
)
{
return
RESULT_NEED_MORE_DATA
;
}
if
(
sampleIndex
<
pendingSeekSyncSampleIndex
)
{
return
skipSample
(
inputStream
);
return
skipSample
(
inputStream
,
sampleSize
);
}
return
readSample
(
inputStream
,
out
);
return
readSample
(
inputStream
,
sampleSize
,
out
);
}
private
int
skipSample
(
NonBlockingInputStream
inputStream
)
{
if
(
parserState
==
STATE_READING_SAMPLE_START
)
{
ParsableByteArray
sampleEncryptionData
=
cencAuxiliaryData
!=
null
?
cencAuxiliaryData
:
fragmentRun
.
smoothStreamingSampleEncryptionData
;
if
(
sampleEncryptionData
!=
null
)
{
TrackEncryptionBox
encryptionBox
=
track
.
sampleDescriptionEncryptionBoxes
[
fragmentRun
.
sampleDescriptionIndex
];
int
vectorSize
=
encryptionBox
.
initializationVectorSize
;
boolean
subsampleEncryption
=
cencAuxiliaryData
!=
null
?
fragmentRun
.
auxiliarySampleInfoSizeTable
[
sampleIndex
]
>
vectorSize
:
fragmentRun
.
smoothStreamingUsesSubsampleEncryption
;
sampleEncryptionData
.
skip
(
vectorSize
);
int
subsampleCount
=
subsampleEncryption
?
sampleEncryptionData
.
readUnsignedShort
()
:
1
;
if
(
subsampleEncryption
)
{
sampleEncryptionData
.
skip
((
2
+
4
)
*
subsampleCount
);
}
private
int
skipSample
(
NonBlockingInputStream
inputStream
,
int
sampleSize
)
{
if
(
fragmentRun
.
definesEncryptionData
)
{
ParsableByteArray
sampleEncryptionData
=
fragmentRun
.
sampleEncryptionData
;
TrackEncryptionBox
encryptionBox
=
track
.
sampleDescriptionEncryptionBoxes
[
fragmentRun
.
sampleDescriptionIndex
];
int
vectorSize
=
encryptionBox
.
initializationVectorSize
;
boolean
subsampleEncryption
=
fragmentRun
.
sampleHasSubsampleEncryptionTable
[
sampleIndex
];
sampleEncryptionData
.
skip
(
vectorSize
);
int
subsampleCount
=
subsampleEncryption
?
sampleEncryptionData
.
readUnsignedShort
()
:
1
;
if
(
subsampleEncryption
)
{
sampleEncryptionData
.
skip
((
2
+
4
)
*
subsampleCount
);
}
}
int
sampleSize
=
fragmentRun
.
sampleSizeTable
[
sampleIndex
];
int
bytesRead
=
inputStream
.
skip
(
sampleSize
-
sampleBytesRead
);
if
(
bytesRead
==
-
1
)
{
return
RESULT_END_OF_STREAM
;
}
sampleBytesRead
+=
bytesRead
;
if
(
sampleSize
!=
sampleBytesRead
)
{
enterState
(
STATE_READING_SAMPLE_INCREMENTAL
);
return
RESULT_NEED_MORE_DATA
;
}
inputStream
.
skip
(
sampleSize
);
sampleIndex
++;
enterState
(
STATE_READING_SAMPLE
_START
);
enterState
(
STATE_READING_SAMPLE
);
return
0
;
}
@SuppressLint
(
"InlinedApi"
)
private
int
readSample
(
NonBlockingInputStream
inputStream
,
SampleHolder
out
)
{
private
int
readSample
(
NonBlockingInputStream
inputStream
,
int
sampleSize
,
SampleHolder
out
)
{
if
(
out
==
null
)
{
return
RESULT_NEED_SAMPLE_HOLDER
;
}
int
sampleSize
=
fragmentRun
.
sampleSizeTable
[
sampleIndex
];
ByteBuffer
outputData
=
out
.
data
;
if
(
parserState
==
STATE_READING_SAMPLE_START
)
{
out
.
timeUs
=
fragmentRun
.
getSamplePresentationTime
(
sampleIndex
)
*
1000L
;
out
.
flags
=
0
;
if
(
fragmentRun
.
sampleIsSyncFrameTable
[
sampleIndex
])
{
out
.
flags
|=
MediaExtractor
.
SAMPLE_FLAG_SYNC
;
lastSyncSampleIndex
=
sampleIndex
;
}
if
(
out
.
allowDataBufferReplacement
&&
(
out
.
data
==
null
||
out
.
data
.
capacity
()
<
sampleSize
))
{
outputData
=
ByteBuffer
.
allocate
(
sampleSize
);
out
.
data
=
outputData
;
}
ParsableByteArray
sampleEncryptionData
=
cencAuxiliaryData
!=
null
?
cencAuxiliaryData
:
fragmentRun
.
smoothStreamingSampleEncryptionData
;
if
(
sampleEncryptionData
!=
null
)
{
readSampleEncryptionData
(
sampleEncryptionData
,
out
);
}
out
.
timeUs
=
fragmentRun
.
getSamplePresentationTime
(
sampleIndex
)
*
1000L
;
out
.
flags
=
0
;
if
(
fragmentRun
.
sampleIsSyncFrameTable
[
sampleIndex
])
{
out
.
flags
|=
MediaExtractor
.
SAMPLE_FLAG_SYNC
;
lastSyncSampleIndex
=
sampleIndex
;
}
int
bytesRead
;
if
(
outputData
==
null
)
{
bytesRead
=
inputStream
.
skip
(
sampleSize
-
sampleBytesRead
);
}
else
{
bytesRead
=
inputStream
.
read
(
outputData
,
sampleSize
-
sampleBytesRead
);
if
(
out
.
allowDataBufferReplacement
&&
(
out
.
data
==
null
||
out
.
data
.
capacity
()
<
sampleSize
))
{
outputData
=
ByteBuffer
.
allocate
(
sampleSize
);
out
.
data
=
outputData
;
}
if
(
bytesRead
==
-
1
)
{
re
turn
RESULT_END_OF_STREAM
;
if
(
fragmentRun
.
definesEncryptionData
)
{
re
adSampleEncryptionData
(
fragmentRun
.
sampleEncryptionData
,
out
)
;
}
sampleBytesRead
+=
bytesRead
;
if
(
sampleSize
!=
sampleBytesRead
)
{
enterState
(
STATE_READING_SAMPLE_INCREMENTAL
);
return
RESULT_NEED_MORE_DATA
|
RESULT_READ_SAMPLE_PARTIAL
;
}
if
(
outputData
!=
null
)
{
if
(
outputData
==
null
)
{
inputStream
.
skip
(
sampleSize
);
out
.
size
=
0
;
}
else
{
inputStream
.
read
(
outputData
,
sampleSize
);
if
(
track
.
type
==
Track
.
TYPE_VIDEO
)
{
// The mp4 file contains length-prefixed NAL units, but the decoder wants start code
// delimited content. Replace length prefixes with start codes.
...
...
@@ -1203,13 +1142,11 @@ public final class FragmentedMp4Extractor {
outputData
.
position
(
sampleOffset
+
sampleSize
);
}
out
.
size
=
sampleSize
;
}
else
{
out
.
size
=
0
;
}
sampleIndex
++;
enterState
(
STATE_READING_SAMPLE
_START
);
return
RESULT_READ_SAMPLE
_FULL
;
enterState
(
STATE_READING_SAMPLE
);
return
RESULT_READ_SAMPLE
;
}
@SuppressLint
(
"InlinedApi"
)
...
...
@@ -1219,9 +1156,7 @@ public final class FragmentedMp4Extractor {
byte
[]
keyId
=
encryptionBox
.
keyId
;
boolean
isEncrypted
=
encryptionBox
.
isEncrypted
;
int
vectorSize
=
encryptionBox
.
initializationVectorSize
;
boolean
subsampleEncryption
=
cencAuxiliaryData
!=
null
?
fragmentRun
.
auxiliarySampleInfoSizeTable
[
sampleIndex
]
>
vectorSize
:
fragmentRun
.
smoothStreamingUsesSubsampleEncryption
;
boolean
subsampleEncryption
=
fragmentRun
.
sampleHasSubsampleEncryptionTable
[
sampleIndex
];
byte
[]
vector
=
out
.
cryptoInfo
.
iv
;
if
(
vector
==
null
||
vector
.
length
!=
16
)
{
...
...
library/src/main/java/com/google/android/exoplayer/parser/mp4/ParsableByteArray.java
View file @
79c2f535
...
...
@@ -23,17 +23,14 @@ import java.nio.ByteBuffer;
*/
/* package */
final
class
ParsableByteArray
{
private
final
byte
[]
data
;
public
byte
[]
data
;
private
int
position
;
public
ParsableByteArray
(
int
length
)
{
this
.
data
=
new
byte
[
length
];
}
public
byte
[]
getData
()
{
return
data
;
}
public
int
length
()
{
return
data
.
length
;
}
...
...
library/src/main/java/com/google/android/exoplayer/parser/mp4/TrackEncryptionBox.java
View file @
79c2f535
...
...
@@ -18,7 +18,7 @@ package com.google.android.exoplayer.parser.mp4;
/**
* Encapsulates information parsed from a track encryption (tenc) box in an MP4 stream.
*/
public
class
TrackEncryptionBox
{
public
final
class
TrackEncryptionBox
{
/**
* Indicates the encryption state of the samples in the sample group.
...
...
library/src/main/java/com/google/android/exoplayer/parser/mp4/TrackFragment.java
View file @
79c2f535
...
...
@@ -15,48 +15,136 @@
*/
package
com
.
google
.
android
.
exoplayer
.
parser
.
mp4
;
import
com.google.android.exoplayer.upstream.NonBlockingInputStream
;
/**
* A holder for information corresponding to a single fragment of an mp4 file.
*/
/* package */
class
TrackFragment
{
/* package */
final
class
TrackFragment
{
public
int
sampleDescriptionIndex
;
/**
* The number of samples contained by the fragment.
*/
public
int
length
;
/**
* The size of each sample in the run.
*/
public
int
[]
sampleSizeTable
;
/**
* The decoding time of each sample in the run.
*/
public
int
[]
sampleDecodingTimeTable
;
/**
* The composition time offset of each sample in the run.
*/
public
int
[]
sampleCompositionTimeOffsetTable
;
/**
* Indicates which samples are sync frames.
*/
public
boolean
[]
sampleIsSyncFrameTable
;
/**
* True if the fragment defines encryption data. False otherwise.
*/
public
boolean
definesEncryptionData
;
/**
* If {@link #definesEncryptionData} is true, indicates which samples use sub-sample encryption.
* Undefined otherwise.
*/
public
boolean
[]
sampleHasSubsampleEncryptionTable
;
/**
* If {@link #definesEncryptionData} is true, indicates the length of the sample encryption data.
* Undefined otherwise.
*/
public
int
sampleEncryptionDataLength
;
/**
* If {@link #definesEncryptionData} is true, contains binary sample encryption data. Undefined
* otherwise.
*/
public
ParsableByteArray
sampleEncryptionData
;
/**
* Whether {@link #sampleEncryptionData} needs populating with the actual encryption data.
*/
public
boolean
sampleEncryptionDataNeedsFill
;
public
int
auxiliarySampleInfoTotalSize
;
public
int
[]
auxiliarySampleInfoSizeTable
;
public
boolean
smoothStreamingUsesSubsampleEncryption
;
public
ParsableByteArray
smoothStreamingSampleEncryptionData
;
/**
* Resets the fragment.
* <p>
* The {@link #length} is set to 0, and both {@link #definesEncryptionData} and
* {@link #sampleEncryptionDataNeedsFill} is set to false.
*/
public
void
reset
()
{
length
=
0
;
definesEncryptionData
=
false
;
sampleEncryptionDataNeedsFill
=
false
;
}
public
void
setSampleDescriptionIndex
(
int
sampleDescriptionIndex
)
{
this
.
sampleDescriptionIndex
=
sampleDescriptionIndex
;
/**
* Configures the fragment for the specified number of samples.
* <p>
* The {@link #length} of the fragment is set to the specified sample count, and the contained
* tables are resized if necessary such that they are at least this length.
*
* @param sampleCount The number of samples in the new run.
*/
public
void
initTables
(
int
sampleCount
)
{
length
=
sampleCount
;
if
(
sampleSizeTable
==
null
||
sampleSizeTable
.
length
<
length
)
{
// Size the tables 25% larger than needed, so as to make future resize operations less
// likely. The choice of 25% is relatively arbitrary.
int
tableSize
=
(
sampleCount
*
125
)
/
100
;
sampleSizeTable
=
new
int
[
tableSize
];
sampleDecodingTimeTable
=
new
int
[
tableSize
];
sampleCompositionTimeOffsetTable
=
new
int
[
tableSize
];
sampleIsSyncFrameTable
=
new
boolean
[
tableSize
];
sampleHasSubsampleEncryptionTable
=
new
boolean
[
tableSize
];
}
}
public
void
setSampleTables
(
int
[]
sampleSizeTable
,
int
[]
sampleDecodingTimeTable
,
int
[]
sampleCompositionTimeOffsetTable
,
boolean
[]
sampleIsSyncFrameTable
)
{
this
.
sampleSizeTable
=
sampleSizeTable
;
this
.
sampleDecodingTimeTable
=
sampleDecodingTimeTable
;
this
.
sampleCompositionTimeOffsetTable
=
sampleCompositionTimeOffsetTable
;
this
.
sampleIsSyncFrameTable
=
sampleIsSyncFrameTable
;
this
.
length
=
sampleSizeTable
.
length
;
/**
* Configures the fragment to be one that defines encryption data of the specified length.
* <p>
* {@link #definesEncryptionData} is set to true, {@link #sampleEncryptionDataLength} is set to
* the specified length, and {@link #sampleEncryptionData} is resized if necessary such that it
* is at least this length.
*
* @param length The length in bytes of the encryption data.
*/
public
void
initEncryptionData
(
int
length
)
{
if
(
sampleEncryptionData
==
null
||
sampleEncryptionData
.
length
()
<
length
)
{
sampleEncryptionData
=
new
ParsableByteArray
(
length
);
}
sampleEncryptionDataLength
=
length
;
definesEncryptionData
=
true
;
sampleEncryptionDataNeedsFill
=
true
;
}
public
void
setAuxiliarySampleInfoTables
(
int
totalAuxiliarySampleInfoSize
,
int
[]
auxiliarySampleInfoSizeTable
)
{
this
.
auxiliarySampleInfoTotalSize
=
totalAuxiliarySampleInfoSize
;
this
.
auxiliarySampleInfoSizeTable
=
auxiliarySampleInfoSizeTable
;
/**
* Fills {@link #sampleEncryptionData} from the provided source.
*
* @param source A source from which to read the encryption data.
*/
public
void
fillEncryptionData
(
ParsableByteArray
source
)
{
source
.
readBytes
(
sampleEncryptionData
.
data
,
0
,
sampleEncryptionDataLength
);
sampleEncryptionData
.
setPosition
(
0
);
sampleEncryptionDataNeedsFill
=
false
;
}
public
void
setSmoothStreamingSampleEncryptionData
(
ParsableByteArray
data
,
boolean
usesSubsampleEncryption
)
{
this
.
smoothStreamingSampleEncryptionData
=
data
;
this
.
smoothStreamingUsesSubsampleEncryption
=
usesSubsampleEncryption
;
/**
* Fills {@link #sampleEncryptionData} for the current run from the provided source.
*
* @param source A source from which to read the encryption data.
* @return True if the encryption data was filled. False if the source had insufficient data.
*/
public
boolean
fillEncryptionData
(
NonBlockingInputStream
source
)
{
if
(
source
.
getAvailableByteCount
()
<
sampleEncryptionDataLength
)
{
return
false
;
}
source
.
read
(
sampleEncryptionData
.
data
,
0
,
sampleEncryptionDataLength
);
sampleEncryptionData
.
setPosition
(
0
);
sampleEncryptionDataNeedsFill
=
false
;
return
true
;
}
public
int
getSamplePresentationTime
(
int
index
)
{
...
...
library/src/main/java/com/google/android/exoplayer/parser/webm/DefaultEbmlReader.java
View file @
79c2f535
...
...
@@ -138,9 +138,8 @@ import java.util.Stack;
while
(
true
)
{
while
(!
masterElementsStack
.
isEmpty
()
&&
bytesRead
>=
masterElementsStack
.
peek
().
elementEndOffsetBytes
)
{
if
(!
eventHandler
.
onMasterElementEnd
(
masterElementsStack
.
pop
().
elementId
))
{
return
READ_RESULT_CONTINUE
;
}
eventHandler
.
onMasterElementEnd
(
masterElementsStack
.
pop
().
elementId
);
return
READ_RESULT_CONTINUE
;
}
if
(
state
==
STATE_BEGIN_READING
)
{
...
...
@@ -161,12 +160,10 @@ import java.util.Stack;
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
;
eventHandler
.
onMasterElementStart
(
elementId
,
elementOffset
,
masterHeaderSize
,
elementContentSize
);
prepareForNextElement
();
return
READ_RESULT_CONTINUE
;
case
TYPE_UNSIGNED_INT:
if
(
elementContentSize
>
MAX_INTEGER_ELEMENT_SIZE_BYTES
)
{
throw
new
IllegalStateException
(
"Invalid integer size "
+
elementContentSize
);
...
...
@@ -177,11 +174,9 @@ import java.util.Stack;
return
intResult
;
}
long
intValue
=
getTempByteArrayValue
((
int
)
elementContentSize
,
false
);
if
(!
eventHandler
.
onIntegerElement
(
elementId
,
intValue
))
{
prepareForNextElement
();
return
READ_RESULT_CONTINUE
;
}
break
;
eventHandler
.
onIntegerElement
(
elementId
,
intValue
);
prepareForNextElement
();
return
READ_RESULT_CONTINUE
;
case
TYPE_FLOAT:
if
(
elementContentSize
!=
VALID_FLOAT32_ELEMENT_SIZE_BYTES
&&
elementContentSize
!=
VALID_FLOAT64_ELEMENT_SIZE_BYTES
)
{
...
...
@@ -199,11 +194,9 @@ import java.util.Stack;
}
else
{
floatValue
=
Double
.
longBitsToDouble
(
valueBits
);
}
if
(!
eventHandler
.
onFloatElement
(
elementId
,
floatValue
))
{
prepareForNextElement
();
return
READ_RESULT_CONTINUE
;
}
break
;
eventHandler
.
onFloatElement
(
elementId
,
floatValue
);
prepareForNextElement
();
return
READ_RESULT_CONTINUE
;
case
TYPE_STRING:
if
(
elementContentSize
>
Integer
.
MAX_VALUE
)
{
throw
new
IllegalStateException
(
...
...
@@ -219,11 +212,9 @@ import java.util.Stack;
}
String
stringValue
=
new
String
(
stringBytes
,
Charset
.
forName
(
"UTF-8"
));
stringBytes
=
null
;
if
(!
eventHandler
.
onStringElement
(
elementId
,
stringValue
))
{
prepareForNextElement
();
return
READ_RESULT_CONTINUE
;
}
break
;
eventHandler
.
onStringElement
(
elementId
,
stringValue
);
prepareForNextElement
();
return
READ_RESULT_CONTINUE
;
case
TYPE_BINARY:
if
(
elementContentSize
>
Integer
.
MAX_VALUE
)
{
throw
new
IllegalStateException
(
...
...
@@ -233,18 +224,17 @@ import java.util.Stack;
return
READ_RESULT_NEED_MORE_DATA
;
}
int
binaryHeaderSize
=
(
int
)
(
bytesRead
-
elementOffset
);
// Header size is 12 bytes max.
boolean
keepGoing
=
eventHandler
.
onBinaryElement
(
boolean
consumed
=
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
)
{
if
(
consumed
)
{
long
expectedBytesRead
=
elementOffset
+
binaryHeaderSize
+
elementContentSize
;
if
(
expectedBytesRead
!=
bytesRead
)
{
throw
new
IllegalStateException
(
"Incorrect total bytes read. Expected "
+
expectedBytesRead
+
" but actually "
+
bytesRead
);
}
prepareForNextElement
();
return
READ_RESULT_CONTINUE
;
}
break
;
return
READ_RESULT_CONTINUE
;
case
TYPE_UNKNOWN:
if
(
elementContentSize
>
Integer
.
MAX_VALUE
)
{
throw
new
IllegalStateException
(
...
...
@@ -254,11 +244,11 @@ import java.util.Stack;
if
(
skipResult
!=
READ_RESULT_CONTINUE
)
{
return
skipResult
;
}
prepareForNextElement
();
break
;
default
:
throw
new
IllegalStateException
(
"Invalid element type "
+
type
);
}
prepareForNextElement
();
}
}
...
...
@@ -508,7 +498,7 @@ import java.util.Stack;
*/
private
int
updateBytesState
(
int
additionalBytesRead
,
int
totalBytes
)
{
if
(
additionalBytesRead
==
-
1
)
{
return
READ_RESULT_END_OF_
FILE
;
return
READ_RESULT_END_OF_
STREAM
;
}
bytesState
+=
additionalBytesRead
;
bytesRead
+=
additionalBytesRead
;
...
...
library/src/main/java/com/google/android/exoplayer/parser/webm/DefaultWebmExtractor.java
View file @
79c2f535
...
...
@@ -25,6 +25,7 @@ import com.google.android.exoplayer.util.MimeTypes;
import
android.annotation.TargetApi
;
import
android.media.MediaExtractor
;
import
java.nio.ByteBuffer
;
import
java.util.Arrays
;
import
java.util.concurrent.TimeUnit
;
...
...
@@ -77,13 +78,15 @@ public final class DefaultWebmExtractor implements WebmExtractor {
private
static
final
int
LACING_FIXED
=
2
;
private
static
final
int
LACING_EBML
=
3
;
private
static
final
int
READ_TERMINATING_RESULTS
=
RESULT_NEED_MORE_DATA
|
RESULT_END_OF_STREAM
|
RESULT_READ_SAMPLE
|
RESULT_NEED_SAMPLE_HOLDER
;
private
final
EbmlReader
reader
;
private
final
byte
[]
simpleBlockTimecodeAndFlags
=
new
byte
[
3
];
private
SampleHolder
tempS
ampleHolder
;
private
boolean
sampleRead
;
private
SampleHolder
s
ampleHolder
;
private
int
readResults
;
private
boolean
prepared
=
false
;
private
long
segmentStartOffsetBytes
=
UNKNOWN
;
private
long
segmentEndOffsetBytes
=
UNKNOWN
;
private
long
timecodeScale
=
1000000L
;
...
...
@@ -105,28 +108,29 @@ public final class DefaultWebmExtractor implements WebmExtractor {
/* 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
;
public
int
read
(
NonBlockingInputStream
inputStream
,
SampleHolder
sampleHolder
)
{
this
.
sampleHolder
=
sampleHolder
;
this
.
readResults
=
0
;
while
((
readResults
&
READ_TERMINATING_RESULTS
)
==
0
)
{
int
ebmlReadResult
=
reader
.
read
(
inputStream
);
if
(
ebmlReadResult
==
EbmlReader
.
READ_RESULT_NEED_MORE_DATA
)
{
readResults
|=
WebmExtractor
.
RESULT_NEED_MORE_DATA
;
}
else
if
(
ebmlReadResult
==
EbmlReader
.
READ_RESULT_END_OF_STREAM
)
{
readResults
|=
WebmExtractor
.
RESULT_END_OF_STREAM
;
}
}
this
.
sampleHolder
=
null
;
return
readResults
;
}
@Override
public
boolean
seekTo
(
long
seekTimeUs
,
boolean
allowNoop
)
{
checkPrepared
();
if
(
allowNoop
&&
cues
!=
null
&&
clusterTimecodeUs
!=
UNKNOWN
&&
simpleBlockTimecodeUs
!=
UNKNOWN
&&
seekTimeUs
>=
simpleBlockTimecodeUs
)
{
int
clusterIndex
=
Arrays
.
binarySearch
(
cues
.
timesUs
,
clusterTimecodeUs
);
...
...
@@ -134,19 +138,19 @@ public final class DefaultWebmExtractor implements WebmExtractor {
return
false
;
}
}
clusterTimecodeUs
=
UNKNOWN
;
simpleBlockTimecodeUs
=
UNKNOWN
;
reader
.
reset
();
return
true
;
}
@Override
public
SegmentIndex
getCues
()
{
checkPrepared
();
public
SegmentIndex
getIndex
()
{
return
cues
;
}
@Override
public
MediaFormat
getFormat
()
{
checkPrepared
();
return
format
;
}
...
...
@@ -196,6 +200,8 @@ public final class DefaultWebmExtractor implements WebmExtractor {
break
;
case
ID_CUES:
cuesSizeBytes
=
headerSizeBytes
+
contentsSizeBytes
;
cueTimesUs
=
new
LongArray
();
cueClusterPositions
=
new
LongArray
();
break
;
default
:
// pass
...
...
@@ -204,11 +210,16 @@ public final class DefaultWebmExtractor implements WebmExtractor {
}
/* package */
boolean
onMasterElementEnd
(
int
id
)
{
if
(
id
==
ID_CUES
)
{
finishPreparing
();
return
false
;
switch
(
id
)
{
case
ID_CUES:
buildCues
();
return
false
;
case
ID_VIDEO:
buildFormat
();
return
true
;
default
:
return
true
;
}
return
true
;
}
/* package */
boolean
onIntegerElement
(
int
id
,
long
value
)
{
...
...
@@ -283,6 +294,12 @@ public final class DefaultWebmExtractor implements WebmExtractor {
// Please refer to http://www.matroska.org/technical/specs/index.html#simpleblock_structure
// for info about how data is organized in a SimpleBlock element.
// If we don't have a sample holder then don't consume the data.
if
(
sampleHolder
==
null
)
{
readResults
|=
RESULT_NEED_SAMPLE_HOLDER
;
return
false
;
}
// Value of trackNumber is not used but needs to be read.
reader
.
readVarint
(
inputStream
);
...
...
@@ -304,10 +321,10 @@ public final class DefaultWebmExtractor implements WebmExtractor {
case
LACING_NONE:
long
elementEndOffsetBytes
=
elementOffsetBytes
+
headerSizeBytes
+
contentsSizeBytes
;
simpleBlockTimecodeUs
=
clusterTimecodeUs
+
timecodeUs
;
tempS
ampleHolder
.
flags
=
keyframe
?
MediaExtractor
.
SAMPLE_FLAG_SYNC
:
0
;
tempS
ampleHolder
.
decodeOnly
=
invisible
;
tempS
ampleHolder
.
timeUs
=
clusterTimecodeUs
+
timecodeUs
;
tempS
ampleHolder
.
size
=
(
int
)
(
elementEndOffsetBytes
-
reader
.
getBytesRead
());
s
ampleHolder
.
flags
=
keyframe
?
MediaExtractor
.
SAMPLE_FLAG_SYNC
:
0
;
s
ampleHolder
.
decodeOnly
=
invisible
;
s
ampleHolder
.
timeUs
=
clusterTimecodeUs
+
timecodeUs
;
s
ampleHolder
.
size
=
(
int
)
(
elementEndOffsetBytes
-
reader
.
getBytesRead
());
break
;
case
LACING_EBML:
case
LACING_FIXED:
...
...
@@ -316,44 +333,63 @@ public final class DefaultWebmExtractor implements WebmExtractor {
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
;
ByteBuffer
outputData
=
sampleHolder
.
data
;
if
(
sampleHolder
.
allowDataBufferReplacement
&&
(
sampleHolder
.
data
==
null
||
sampleHolder
.
data
.
capacity
()
<
sampleHolder
.
size
))
{
outputData
=
ByteBuffer
.
allocate
(
sampleHolder
.
size
);
sampleHolder
.
data
=
outputData
;
}
if
(
outputData
==
null
)
{
reader
.
skipBytes
(
inputStream
,
sampleHolder
.
size
);
sampleHolder
.
size
=
0
;
}
else
{
reader
.
readBytes
(
inputStream
,
outputData
,
sampleHolder
.
size
);
}
readResults
|=
RESULT_READ_SAMPLE
;
}
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"
);
/**
* Build a video {@link MediaFormat} containing recently gathered Video information, if needed.
*
* <p>Replaces the previous {@link #format} only if video width/height have changed.
* {@link #format} is guaranteed to not be null after calling this method. In
* the event that it can't be built, an {@link IllegalStateException} will be thrown.
*/
private
void
buildFormat
()
{
if
(
pixelWidth
!=
UNKNOWN
&&
pixelHeight
!=
UNKNOWN
&&
(
format
==
null
||
format
.
width
!=
pixelWidth
||
format
.
height
!=
pixelHeight
))
{
format
=
MediaFormat
.
createVideoFormat
(
MimeTypes
.
VIDEO_VP9
,
MediaFormat
.
NO_VALUE
,
pixelWidth
,
pixelHeight
,
null
);
readResults
|=
RESULT_READ_INIT
;
}
else
if
(
format
==
null
)
{
throw
new
IllegalStateException
(
"Unable to build format"
);
}
}
private
void
finishPreparing
()
{
if
(
prepared
)
{
throw
new
IllegalStateException
(
"Already prepared"
);
}
else
if
(
segmentStartOffsetBytes
==
UNKNOWN
)
{
/**
* Build a {@link SegmentIndex} containing recently gathered Cues information.
*
* <p>{@link #cues} is guaranteed to not be null after calling this method. In
* the event that it can't be built, an {@link IllegalStateException} will be thrown.
*/
private
void
buildCues
()
{
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
())
{
}
else
if
(
cueTimesUs
==
null
||
cueClusterPositions
==
null
||
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
];
...
...
@@ -372,8 +408,7 @@ public final class DefaultWebmExtractor implements WebmExtractor {
cues
=
new
SegmentIndex
((
int
)
cuesSizeBytes
,
sizes
,
offsets
,
durationsUs
,
timesUs
);
cueTimesUs
=
null
;
cueClusterPositions
=
null
;
prepared
=
true
;
readResults
|=
RESULT_READ_INDEX
;
}
/**
...
...
@@ -388,30 +423,30 @@ public final class DefaultWebmExtractor implements WebmExtractor {
}
@Override
public
boolean
onMasterElementStart
(
public
void
onMasterElementStart
(
int
id
,
long
elementOffsetBytes
,
int
headerSizeBytes
,
long
contentsSizeBytes
)
{
return
DefaultWebmExtractor
.
this
.
onMasterElementStart
(
DefaultWebmExtractor
.
this
.
onMasterElementStart
(
id
,
elementOffsetBytes
,
headerSizeBytes
,
contentsSizeBytes
);
}
@Override
public
boolean
onMasterElementEnd
(
int
id
)
{
return
DefaultWebmExtractor
.
this
.
onMasterElementEnd
(
id
);
public
void
onMasterElementEnd
(
int
id
)
{
DefaultWebmExtractor
.
this
.
onMasterElementEnd
(
id
);
}
@Override
public
boolean
onIntegerElement
(
int
id
,
long
value
)
{
return
DefaultWebmExtractor
.
this
.
onIntegerElement
(
id
,
value
);
public
void
onIntegerElement
(
int
id
,
long
value
)
{
DefaultWebmExtractor
.
this
.
onIntegerElement
(
id
,
value
);
}
@Override
public
boolean
onFloatElement
(
int
id
,
double
value
)
{
return
DefaultWebmExtractor
.
this
.
onFloatElement
(
id
,
value
);
public
void
onFloatElement
(
int
id
,
double
value
)
{
DefaultWebmExtractor
.
this
.
onFloatElement
(
id
,
value
);
}
@Override
public
boolean
onStringElement
(
int
id
,
String
value
)
{
return
DefaultWebmExtractor
.
this
.
onStringElement
(
id
,
value
);
public
void
onStringElement
(
int
id
,
String
value
)
{
DefaultWebmExtractor
.
this
.
onStringElement
(
id
,
value
);
}
@Override
...
...
library/src/main/java/com/google/android/exoplayer/parser/webm/EbmlEventHandler.java
View file @
79c2f535
...
...
@@ -46,9 +46,8 @@ import java.nio.ByteBuffer;
* @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
(
public
void
onMasterElementStart
(
int
id
,
long
elementOffsetBytes
,
int
headerSizeBytes
,
long
contentsSizeBytes
);
/**
...
...
@@ -56,44 +55,42 @@ import java.nio.ByteBuffer;
* {@link NonBlockingInputStream}.
*
* @param id The integer ID of this element
* @return {@code false} if parsing should stop right away
*/
public
boolean
onMasterElementEnd
(
int
id
);
public
void
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
);
public
void
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
);
public
void
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
);
public
void
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}.
* Subclasses must either read nothing and return {@code false}, or exactly read the entire
* contents of the element, which is {@code contentsSizeBytes} in length, and return {@code true}.
*
* <p>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:
...
...
@@ -111,7 +108,7 @@ import java.nio.ByteBuffer;
* @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
* @return
True if the element was read. False otherwise.
*/
public
boolean
onBinaryElement
(
int
id
,
long
elementOffsetBytes
,
int
headerSizeBytes
,
int
contentsSizeBytes
,
...
...
library/src/main/java/com/google/android/exoplayer/parser/webm/EbmlReader.java
View file @
79c2f535
...
...
@@ -44,12 +44,12 @@ import java.nio.ByteBuffer;
// Return values for reading methods.
public
static
final
int
READ_RESULT_CONTINUE
=
0
;
public
static
final
int
READ_RESULT_NEED_MORE_DATA
=
1
;
public
static
final
int
READ_RESULT_END_OF_
FILE
=
2
;
public
static
final
int
READ_RESULT_END_OF_
STREAM
=
2
;
public
void
setEventHandler
(
EbmlEventHandler
eventHandler
);
/**
* Reads from a {@link NonBlockingInputStream}
and calls event callbacks as needed
.
* Reads from a {@link NonBlockingInputStream}
, invoking an event callback if possible
.
*
* @param inputStream The input stream from which data should be read
* @return One of the {@code RESULT_*} flags defined in this interface
...
...
library/src/main/java/com/google/android/exoplayer/parser/webm/WebmExtractor.java
View file @
79c2f535
...
...
@@ -30,24 +30,38 @@ import com.google.android.exoplayer.upstream.NonBlockingInputStream;
public
interface
WebmExtractor
{
/**
* Whether the has parsed the cues and sample format from the stream.
*
* @return True if the extractor is prepared. False otherwise
* An attempt to read from the input stream returned insufficient data.
*/
public
static
final
int
RESULT_NEED_MORE_DATA
=
1
;
/**
* The end of the input stream was reached.
*/
public
static
final
int
RESULT_END_OF_STREAM
=
2
;
/**
* A media sample was read.
*/
public
static
final
int
RESULT_READ_SAMPLE
=
4
;
/**
* Initialization data was read. The parsed data can be read using {@link #getFormat()}.
*/
public
static
final
int
RESULT_READ_INIT
=
8
;
/**
* A sidx atom was read. The parsed data can be read using {@link #getIndex()}.
*/
public
boolean
isPrepared
();
public
static
final
int
RESULT_READ_INDEX
=
16
;
/**
* The next thing to be read is a sample, but a {@link SampleHolder} was not supplied.
*/
public
static
final
int
RESULT_NEED_SAMPLE_HOLDER
=
32
;
/**
* Consumes data from a {@link NonBlockingInputStream}.
*
* <p>If the return value is {@code false}, then a sample may have been partially read into
* {@code sampleHolder}. Hence the same {@link SampleHolder} instance must be passed
* in subsequent calls until the whole sample has been read.
*
* @param inputStream The input stream from which data 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
* @return
One or more of the {@code RESULT_*} flags defined in this class.
*/
public
boolean
read
(
NonBlockingInputStream
inputStream
,
SampleHolder
sampleHolder
);
public
int
read
(
NonBlockingInputStream
inputStream
,
SampleHolder
sampleHolder
);
/**
* Seeks to a position before or equal to the requested time.
...
...
@@ -66,7 +80,7 @@ public interface WebmExtractor {
* @return The cues in the form of a {@link SegmentIndex}, or null if the extractor is not yet
* prepared
*/
public
SegmentIndex
get
Cues
();
public
SegmentIndex
get
Index
();
/**
* Returns the format of the samples contained within the media stream.
...
...
library/src/main/java/com/google/android/exoplayer/text/TextTrackRenderer.java
View file @
79c2f535
...
...
@@ -144,7 +144,12 @@ public class TextTrackRenderer extends TrackRenderer implements Callback {
@Override
protected
void
doSomeWork
(
long
timeUs
)
throws
ExoPlaybackException
{
source
.
continueBuffering
(
timeUs
);
try
{
source
.
continueBuffering
(
timeUs
);
}
catch
(
IOException
e
)
{
throw
new
ExoPlaybackException
(
e
);
}
currentPositionUs
=
timeUs
;
// We're iterating through the events in a subtitle. Set textRendererNeedsUpdate if we advance
...
...
@@ -225,7 +230,7 @@ public class TextTrackRenderer extends TrackRenderer implements Callback {
@Override
protected
long
getBufferedPositionUs
()
{
// Don't block playback whilst subtitles are loading.
return
END_OF_TRACK
;
return
END_OF_TRACK
_US
;
}
@Override
...
...
@@ -275,7 +280,6 @@ public class TextTrackRenderer extends TrackRenderer implements Callback {
}
}
@SuppressWarnings
(
"unchecked"
)
@Override
public
boolean
handleMessage
(
Message
msg
)
{
switch
(
msg
.
what
)
{
...
...
library/src/main/java/com/google/android/exoplayer/upstream/Allocation.java
View file @
79c2f535
...
...
@@ -25,6 +25,28 @@ package com.google.android.exoplayer.upstream;
public
interface
Allocation
{
/**
* Ensures the allocation has a capacity greater than or equal to the specified size in bytes.
* <p>
* If {@code size} is greater than the current capacity of the allocation, then it will grow
* to have a capacity of at least {@code size}. The allocation is grown by adding new fragments.
* Existing fragments remain unchanged, and any data that has been written to them will be
* preserved.
* <p>
* If {@code size} is less than or equal to the capacity of the allocation, then the call is a
* no-op.
*
* @param size The minimum required capacity, in bytes.
*/
public
void
ensureCapacity
(
int
size
);
/**
* Gets the capacity of the allocation, in bytes.
*
* @return The capacity of the allocation, in bytes.
*/
public
int
capacity
();
/**
* Gets the buffers in which the fragments are allocated.
*
* @return The buffers in which the fragments are allocated.
...
...
library/src/main/java/com/google/android/exoplayer/upstream/BandwidthMeter.java
View file @
79c2f535
...
...
@@ -26,10 +26,10 @@ public interface BandwidthMeter {
final
long
NO_ESTIMATE
=
-
1
;
/**
* Gets the estimated bandwidth.
* Gets the estimated bandwidth
, in bits/sec
.
*
* @return Estimated bandwidth in b
yte
s/sec, or {@link #NO_ESTIMATE} if no estimate is available.
* @return Estimated bandwidth in b
it
s/sec, or {@link #NO_ESTIMATE} if no estimate is available.
*/
long
getEstimate
();
long
get
Bitrate
Estimate
();
}
library/src/main/java/com/google/android/exoplayer/upstream/BufferPool.java
View file @
79c2f535
...
...
@@ -67,15 +67,39 @@ public final class BufferPool implements Allocator {
@Override
public
synchronized
Allocation
allocate
(
int
size
)
{
return
new
AllocationImpl
(
allocate
(
size
,
null
));
}
/**
* Allocates byte arrays whose combined length is at least {@code size}.
* <p>
* An existing array of byte arrays may be provided to form the start of the allocation.
*
* @param size The total size required, in bytes.
* @param existing Existing byte arrays to use as the start of the allocation. May be null.
* @return The allocated byte arrays.
*/
/* package */
synchronized
byte
[][]
allocate
(
int
size
,
byte
[][]
existing
)
{
int
requiredBufferCount
=
requiredBufferCount
(
size
);
allocatedBufferCount
+=
requiredBufferCount
;
if
(
existing
!=
null
&&
requiredBufferCount
<=
existing
.
length
)
{
// The existing buffers are sufficient.
return
existing
;
}
// We need to allocate additional buffers.
byte
[][]
buffers
=
new
byte
[
requiredBufferCount
][];
for
(
int
i
=
0
;
i
<
requiredBufferCount
;
i
++)
{
int
firstNewBufferIndex
=
0
;
if
(
existing
!=
null
)
{
firstNewBufferIndex
=
existing
.
length
;
System
.
arraycopy
(
existing
,
0
,
buffers
,
0
,
firstNewBufferIndex
);
}
// Allocate the new buffers
allocatedBufferCount
+=
requiredBufferCount
-
firstNewBufferIndex
;
for
(
int
i
=
firstNewBufferIndex
;
i
<
requiredBufferCount
;
i
++)
{
// Use a recycled buffer if one is available. Else instantiate a new one.
buffers
[
i
]
=
recycledBufferCount
>
0
?
recycledBuffers
[--
recycledBufferCount
]
:
new
byte
[
bufferLength
];
}
return
new
AllocationImpl
(
buffers
)
;
return
buffers
;
}
/**
...
...
@@ -113,6 +137,16 @@ public final class BufferPool implements Allocator {
}
@Override
public
void
ensureCapacity
(
int
size
)
{
buffers
=
allocate
(
size
,
buffers
);
}
@Override
public
int
capacity
()
{
return
bufferLength
*
buffers
.
length
;
}
@Override
public
byte
[][]
getBuffers
()
{
return
buffers
;
}
...
...
library/src/main/java/com/google/android/exoplayer/upstream/ByteArrayDataSink.java
View file @
79c2f535
...
...
@@ -15,6 +15,7 @@
*/
package
com
.
google
.
android
.
exoplayer
.
upstream
;
import
com.google.android.exoplayer.C
;
import
com.google.android.exoplayer.util.Assertions
;
import
java.io.ByteArrayOutputStream
;
...
...
@@ -29,7 +30,7 @@ public class ByteArrayDataSink implements DataSink {
@Override
public
DataSink
open
(
DataSpec
dataSpec
)
throws
IOException
{
if
(
dataSpec
.
length
==
DataSpec
.
LENGTH_UNBOUNDED
)
{
if
(
dataSpec
.
length
==
C
.
LENGTH_UNBOUNDED
)
{
stream
=
new
ByteArrayOutputStream
();
}
else
{
Assertions
.
checkArgument
(
dataSpec
.
length
<=
Integer
.
MAX_VALUE
);
...
...
library/src/main/java/com/google/android/exoplayer/upstream/ByteArrayDataSource.java
View file @
79c2f535
...
...
@@ -15,6 +15,7 @@
*/
package
com
.
google
.
android
.
exoplayer
.
upstream
;
import
com.google.android.exoplayer.C
;
import
com.google.android.exoplayer.util.Assertions
;
import
java.io.IOException
;
...
...
@@ -36,14 +37,14 @@ public class ByteArrayDataSource implements DataSource {
@Override
public
long
open
(
DataSpec
dataSpec
)
throws
IOException
{
if
(
dataSpec
.
length
==
DataSpec
.
LENGTH_UNBOUNDED
)
{
if
(
dataSpec
.
length
==
C
.
LENGTH_UNBOUNDED
)
{
Assertions
.
checkArgument
(
dataSpec
.
position
<
data
.
length
);
}
else
{
Assertions
.
checkArgument
(
dataSpec
.
position
+
dataSpec
.
length
<=
data
.
length
);
}
readPosition
=
(
int
)
dataSpec
.
position
;
return
(
dataSpec
.
length
==
DataSpec
.
LENGTH_UNBOUNDED
)
?
(
data
.
length
-
dataSpec
.
position
)
:
dataSpec
.
length
;
return
(
dataSpec
.
length
==
C
.
LENGTH_UNBOUNDED
)
?
(
data
.
length
-
dataSpec
.
position
)
:
dataSpec
.
length
;
}
@Override
...
...
library/src/main/java/com/google/android/exoplayer/upstream/DataSource.java
View file @
79c2f535
...
...
@@ -15,6 +15,8 @@
*/
package
com
.
google
.
android
.
exoplayer
.
upstream
;
import
com.google.android.exoplayer.C
;
import
java.io.IOException
;
/**
...
...
@@ -34,9 +36,10 @@ public interface DataSource {
* @param dataSpec Defines the data to be read.
* @throws IOException If an error occurs opening the source.
* @return The number of bytes that can be read from the opened source. For unbounded requests
* (i.e. requests where {@link DataSpec#length} equals {@link DataSpec#LENGTH_UNBOUNDED})
* this value is the resolved length of the request. For all other requests, the value
* returned will be equal to the request's {@link DataSpec#length}.
* (i.e. requests where {@link DataSpec#length} equals {@link C#LENGTH_UNBOUNDED}) this value
* is the resolved length of the request, or {@link C#LENGTH_UNBOUNDED} if the length is still
* unresolved. For all other requests, the value returned will be equal to the request's
* {@link DataSpec#length}.
*/
public
long
open
(
DataSpec
dataSpec
)
throws
IOException
;
...
...
library/src/main/java/com/google/android/exoplayer/upstream/DataSourceStream.java
View file @
79c2f535
...
...
@@ -15,6 +15,7 @@
*/
package
com
.
google
.
android
.
exoplayer
.
upstream
;
import
com.google.android.exoplayer.C
;
import
com.google.android.exoplayer.upstream.Loader.Loadable
;
import
com.google.android.exoplayer.util.Assertions
;
import
com.google.android.exoplayer.util.Util
;
...
...
@@ -39,6 +40,8 @@ public final class DataSourceStream implements Loadable, NonBlockingInputStream
}
private
static
final
int
CHUNKED_ALLOCATION_INCREMENT
=
256
*
1024
;
private
final
DataSource
dataSource
;
private
final
DataSpec
dataSpec
;
private
final
Allocator
allocator
;
...
...
@@ -57,7 +60,7 @@ public final class DataSourceStream implements Loadable, NonBlockingInputStream
/**
* @param dataSource The source from which the data should be loaded.
* @param dataSpec Defines the data to be loaded. {@code dataSpec.length} must not exceed
* {@link Integer#MAX_VALUE}. If {@code dataSpec.length ==
DataSpec
.LENGTH_UNBOUNDED} then
* {@link Integer#MAX_VALUE}. If {@code dataSpec.length ==
C
.LENGTH_UNBOUNDED} then
* the length resolved by {@code dataSource.open(dataSpec)} must not exceed
* {@link Integer#MAX_VALUE}.
* @param allocator Used to obtain an {@link Allocation} for holding the data.
...
...
@@ -67,7 +70,7 @@ public final class DataSourceStream implements Loadable, NonBlockingInputStream
this
.
dataSource
=
dataSource
;
this
.
dataSpec
=
dataSpec
;
this
.
allocator
=
allocator
;
resolvedLength
=
DataSpec
.
LENGTH_UNBOUNDED
;
resolvedLength
=
C
.
LENGTH_UNBOUNDED
;
readHead
=
new
ReadHead
();
}
...
...
@@ -97,13 +100,14 @@ public final class DataSourceStream implements Loadable, NonBlockingInputStream
}
/**
* Returns the length of the streamin bytes.
* Returns the length of the stream in bytes, or {@value C#LENGTH_UNBOUNDED} if the length has
* yet to be determined.
*
* @return The length of the stream in bytes, or {@value
DataSpec#LENGTH_UNBOUNDED} if the length
*
has
yet to be determined.
* @return The length of the stream in bytes, or {@value
C#LENGTH_UNBOUNDED} if the length has
* yet to be determined.
*/
public
long
getLength
()
{
return
resolvedLength
!=
DataSpec
.
LENGTH_UNBOUNDED
?
resolvedLength
:
dataSpec
.
length
;
return
resolvedLength
!=
C
.
LENGTH_UNBOUNDED
?
resolvedLength
:
dataSpec
.
length
;
}
/**
...
...
@@ -112,7 +116,7 @@ public final class DataSourceStream implements Loadable, NonBlockingInputStream
* @return True if the stream has finished loading. False otherwise.
*/
public
boolean
isLoadFinished
()
{
return
resolvedLength
!=
DataSpec
.
LENGTH_UNBOUNDED
&&
loadPosition
==
resolvedLength
;
return
resolvedLength
!=
C
.
LENGTH_UNBOUNDED
&&
loadPosition
==
resolvedLength
;
}
/**
...
...
@@ -123,7 +127,7 @@ public final class DataSourceStream implements Loadable, NonBlockingInputStream
* Note: The read methods provide a more efficient way of consuming the loaded data. Use this
* method only when a freshly allocated byte[] containing all of the loaded data is required.
*
* @return The loaded data or null.
* @return The loaded data
,
or null.
*/
public
final
byte
[]
getLoadedData
()
{
if
(
loadPosition
==
0
)
{
...
...
@@ -144,7 +148,7 @@ public final class DataSourceStream implements Loadable, NonBlockingInputStream
@Override
public
boolean
isEndOfStream
()
{
return
resolvedLength
!=
DataSpec
.
LENGTH_UNBOUNDED
&&
readHead
.
position
==
resolvedLength
;
return
resolvedLength
!=
C
.
LENGTH_UNBOUNDED
&&
readHead
.
position
==
resolvedLength
;
}
@Override
...
...
@@ -191,6 +195,11 @@ public final class DataSourceStream implements Loadable, NonBlockingInputStream
int
bytesRead
=
0
;
byte
[][]
buffers
=
allocation
.
getBuffers
();
while
(
bytesRead
<
bytesToRead
)
{
if
(
readHead
.
fragmentRemaining
==
0
)
{
readHead
.
fragmentIndex
++;
readHead
.
fragmentOffset
=
allocation
.
getFragmentOffset
(
readHead
.
fragmentIndex
);
readHead
.
fragmentRemaining
=
allocation
.
getFragmentLength
(
readHead
.
fragmentIndex
);
}
int
bufferReadLength
=
Math
.
min
(
readHead
.
fragmentRemaining
,
bytesToRead
-
bytesRead
);
if
(
target
!=
null
)
{
target
.
put
(
buffers
[
readHead
.
fragmentIndex
],
readHead
.
fragmentOffset
,
bufferReadLength
);
...
...
@@ -203,11 +212,6 @@ public final class DataSourceStream implements Loadable, NonBlockingInputStream
bytesRead
+=
bufferReadLength
;
readHead
.
fragmentOffset
+=
bufferReadLength
;
readHead
.
fragmentRemaining
-=
bufferReadLength
;
if
(
readHead
.
fragmentRemaining
==
0
&&
readHead
.
position
<
resolvedLength
)
{
readHead
.
fragmentIndex
++;
readHead
.
fragmentOffset
=
allocation
.
getFragmentOffset
(
readHead
.
fragmentIndex
);
readHead
.
fragmentRemaining
=
allocation
.
getFragmentLength
(
readHead
.
fragmentIndex
);
}
}
return
bytesRead
;
...
...
@@ -231,23 +235,32 @@ public final class DataSourceStream implements Loadable, NonBlockingInputStream
// The load was canceled, or is already complete.
return
;
}
try
{
DataSpec
loadDataSpec
;
if
(
resolvedLength
==
DataSpec
.
LENGTH_UNBOUNDED
)
{
if
(
loadPosition
==
0
&&
resolvedLength
==
C
.
LENGTH_UNBOUNDED
)
{
loadDataSpec
=
dataSpec
;
resolvedLength
=
dataSource
.
open
(
loadDataSpec
);
long
resolvedLength
=
dataSource
.
open
(
loadDataSpec
);
if
(
resolvedLength
>
Integer
.
MAX_VALUE
)
{
throw
new
DataSourceStreamLoadException
(
new
UnexpectedLengthException
(
dataSpec
.
length
,
resolvedLength
));
}
this
.
resolvedLength
=
resolvedLength
;
}
else
{
long
remainingLength
=
resolvedLength
!=
C
.
LENGTH_UNBOUNDED
?
resolvedLength
-
loadPosition
:
C
.
LENGTH_UNBOUNDED
;
loadDataSpec
=
new
DataSpec
(
dataSpec
.
uri
,
dataSpec
.
position
+
loadPosition
,
re
solvedLength
-
loadPosition
,
dataSpec
.
key
);
re
mainingLength
,
dataSpec
.
key
);
dataSource
.
open
(
loadDataSpec
);
}
if
(
allocation
==
null
)
{
allocation
=
allocator
.
allocate
((
int
)
resolvedLength
);
int
initialAllocationSize
=
resolvedLength
!=
C
.
LENGTH_UNBOUNDED
?
(
int
)
resolvedLength
:
CHUNKED_ALLOCATION_INCREMENT
;
allocation
=
allocator
.
allocate
(
initialAllocationSize
);
}
int
allocationCapacity
=
allocation
.
capacity
();
if
(
loadPosition
==
0
)
{
writeFragmentIndex
=
0
;
writeFragmentOffset
=
allocation
.
getFragmentOffset
(
0
);
...
...
@@ -256,22 +269,28 @@ public final class DataSourceStream implements Loadable, NonBlockingInputStream
int
read
=
Integer
.
MAX_VALUE
;
byte
[][]
buffers
=
allocation
.
getBuffers
();
while
(!
loadCanceled
&&
loadPosition
<
resolvedLength
&&
read
>
0
)
{
while
(!
loadCanceled
&&
read
>
0
&&
maybeMoreToLoad
()
)
{
if
(
Thread
.
interrupted
())
{
throw
new
InterruptedException
();
}
int
writeLength
=
(
int
)
Math
.
min
(
writeFragmentRemainingLength
,
resolvedLength
-
loadPosition
);
read
=
dataSource
.
read
(
buffers
[
writeFragmentIndex
],
writeFragmentOffset
,
writeLength
);
read
=
dataSource
.
read
(
buffers
[
writeFragmentIndex
],
writeFragmentOffset
,
writeFragmentRemainingLength
);
if
(
read
>
0
)
{
loadPosition
+=
read
;
writeFragmentOffset
+=
read
;
writeFragmentRemainingLength
-=
read
;
if
(
writeFragmentRemainingLength
==
0
&&
loadPosition
<
resolvedLength
)
{
if
(
writeFragmentRemainingLength
==
0
&&
maybeMoreToLoad
()
)
{
writeFragmentIndex
++;
if
(
loadPosition
==
allocationCapacity
)
{
allocation
.
ensureCapacity
(
allocationCapacity
+
CHUNKED_ALLOCATION_INCREMENT
);
allocationCapacity
=
allocation
.
capacity
();
buffers
=
allocation
.
getBuffers
();
}
writeFragmentOffset
=
allocation
.
getFragmentOffset
(
writeFragmentIndex
);
writeFragmentRemainingLength
=
allocation
.
getFragmentLength
(
writeFragmentIndex
);
}
}
else
if
(
resolvedLength
==
C
.
LENGTH_UNBOUNDED
)
{
resolvedLength
=
loadPosition
;
}
else
if
(
resolvedLength
!=
loadPosition
)
{
throw
new
DataSourceStreamLoadException
(
new
UnexpectedLengthException
(
resolvedLength
,
loadPosition
));
...
...
@@ -282,6 +301,10 @@ public final class DataSourceStream implements Loadable, NonBlockingInputStream
}
}
private
boolean
maybeMoreToLoad
()
{
return
resolvedLength
==
C
.
LENGTH_UNBOUNDED
||
loadPosition
<
resolvedLength
;
}
private
static
class
ReadHead
{
private
int
position
;
...
...
library/src/main/java/com/google/android/exoplayer/upstream/DataSpec.java
View file @
79c2f535
...
...
@@ -15,6 +15,7 @@
*/
package
com
.
google
.
android
.
exoplayer
.
upstream
;
import
com.google.android.exoplayer.C
;
import
com.google.android.exoplayer.util.Assertions
;
import
android.net.Uri
;
...
...
@@ -25,13 +26,6 @@ import android.net.Uri;
public
final
class
DataSpec
{
/**
* A permitted value of {@link #length}. A {@link DataSpec} defined with this length represents
* the region of media data that starts at its {@link #position} and extends to the end of the
* data whose location is defined by its {@link #uri}.
*/
public
static
final
int
LENGTH_UNBOUNDED
=
-
1
;
/**
* Identifies the source from which data should be read.
*/
public
final
Uri
uri
;
...
...
@@ -50,7 +44,7 @@ public final class DataSpec {
*/
public
final
long
position
;
/**
* The length of the data. Greater than zero, or equal to {@link #LENGTH_UNBOUNDED}.
* The length of the data. Greater than zero, or equal to {@link
C
#LENGTH_UNBOUNDED}.
*/
public
final
long
length
;
/**
...
...
@@ -98,7 +92,7 @@ public final class DataSpec {
boolean
uriIsFullStream
)
{
Assertions
.
checkArgument
(
absoluteStreamPosition
>=
0
);
Assertions
.
checkArgument
(
position
>=
0
);
Assertions
.
checkArgument
(
length
>
0
||
length
==
LENGTH_UNBOUNDED
);
Assertions
.
checkArgument
(
length
>
0
||
length
==
C
.
LENGTH_UNBOUNDED
);
Assertions
.
checkArgument
(
absoluteStreamPosition
==
position
||
!
uriIsFullStream
);
this
.
uri
=
uri
;
this
.
uriIsFullStream
=
uriIsFullStream
;
...
...
library/src/main/java/com/google/android/exoplayer/upstream/DefaultBandwidthMeter.java
View file @
79c2f535
...
...
@@ -38,11 +38,11 @@ public class DefaultBandwidthMeter implements BandwidthMeter, TransferListener {
*
* @param elapsedMs The time taken to transfer the bytes, in milliseconds.
* @param bytes The number of bytes transferred.
* @param b
andwidthEstimate The estimated bandwidth in bytes/sec, or {@link #NO_ESTIMATE} if no
*
estimate is available. Note that this estimate is typically derived from more informatio
n
*
than
{@code bytes} and {@code elapsedMs}.
* @param b
itrate The estimated bitrate in bits/sec, or {@link #NO_ESTIMATE} if no estimate
*
is available. Note that this estimate is typically derived from more information tha
n
* {@code bytes} and {@code elapsedMs}.
*/
void
onBandwidthSample
(
int
elapsedMs
,
long
bytes
,
long
b
andwidthEstim
ate
);
void
onBandwidthSample
(
int
elapsedMs
,
long
bytes
,
long
b
itr
ate
);
}
...
...
@@ -53,9 +53,9 @@ public class DefaultBandwidthMeter implements BandwidthMeter, TransferListener {
private
final
Clock
clock
;
private
final
SlidingPercentile
slidingPercentile
;
private
long
a
ccumulator
;
private
long
bytesA
ccumulator
;
private
long
startTimeMs
;
private
long
b
andwidth
Estimate
;
private
long
b
itrate
Estimate
;
private
int
streamCount
;
public
DefaultBandwidthMeter
()
{
...
...
@@ -80,17 +80,12 @@ public class DefaultBandwidthMeter implements BandwidthMeter, TransferListener {
this
.
eventListener
=
eventListener
;
this
.
clock
=
clock
;
this
.
slidingPercentile
=
new
SlidingPercentile
(
maxWeight
);
b
andwidth
Estimate
=
NO_ESTIMATE
;
b
itrate
Estimate
=
NO_ESTIMATE
;
}
/**
* Gets the estimated bandwidth.
*
* @return Estimated bandwidth in bytes/sec, or {@link #NO_ESTIMATE} if no estimate is available.
*/
@Override
public
synchronized
long
getEstimate
()
{
return
b
andwidth
Estimate
;
public
synchronized
long
get
Bitrate
Estimate
()
{
return
b
itrate
Estimate
;
}
@Override
...
...
@@ -103,7 +98,7 @@ public class DefaultBandwidthMeter implements BandwidthMeter, TransferListener {
@Override
public
synchronized
void
onBytesTransferred
(
int
bytes
)
{
a
ccumulator
+=
bytes
;
bytesA
ccumulator
+=
bytes
;
}
@Override
...
...
@@ -112,32 +107,26 @@ public class DefaultBandwidthMeter implements BandwidthMeter, TransferListener {
long
nowMs
=
clock
.
elapsedRealtime
();
int
elapsedMs
=
(
int
)
(
nowMs
-
startTimeMs
);
if
(
elapsedMs
>
0
)
{
float
b
ytesPerSecond
=
accumulator
*
1000
/
elapsedMs
;
slidingPercentile
.
addSample
(
computeWeight
(
accumulator
),
byte
sPerSecond
);
float
b
itsPerSecond
=
(
bytesAccumulator
*
8000
)
/
elapsedMs
;
slidingPercentile
.
addSample
(
(
int
)
Math
.
sqrt
(
bytesAccumulator
),
bit
sPerSecond
);
float
bandwidthEstimateFloat
=
slidingPercentile
.
getPercentile
(
0.5f
);
b
andwidth
Estimate
=
Float
.
isNaN
(
bandwidthEstimateFloat
)
?
NO_ESTIMATE
b
itrate
Estimate
=
Float
.
isNaN
(
bandwidthEstimateFloat
)
?
NO_ESTIMATE
:
(
long
)
bandwidthEstimateFloat
;
notifyBandwidthSample
(
elapsedMs
,
accumulator
,
bandwidth
Estimate
);
notifyBandwidthSample
(
elapsedMs
,
bytesAccumulator
,
bitrate
Estimate
);
}
streamCount
--;
if
(
streamCount
>
0
)
{
startTimeMs
=
nowMs
;
}
accumulator
=
0
;
}
// TODO: Use media time (bytes / mediaRate) as weight.
private
int
computeWeight
(
long
mediaBytes
)
{
return
(
int
)
Math
.
sqrt
(
mediaBytes
);
bytesAccumulator
=
0
;
}
private
void
notifyBandwidthSample
(
final
int
elapsedMs
,
final
long
bytes
,
final
long
bandwidthEstimate
)
{
private
void
notifyBandwidthSample
(
final
int
elapsedMs
,
final
long
bytes
,
final
long
bitrate
)
{
if
(
eventHandler
!=
null
&&
eventListener
!=
null
)
{
eventHandler
.
post
(
new
Runnable
()
{
@Override
public
void
run
()
{
eventListener
.
onBandwidthSample
(
elapsedMs
,
bytes
,
b
andwidthEstim
ate
);
eventListener
.
onBandwidthSample
(
elapsedMs
,
bytes
,
b
itr
ate
);
}
});
}
...
...
library/src/main/java/com/google/android/exoplayer/upstream/FileDataSource.java
View file @
79c2f535
...
...
@@ -15,6 +15,8 @@
*/
package
com
.
google
.
android
.
exoplayer
.
upstream
;
import
com.google.android.exoplayer.C
;
import
java.io.IOException
;
import
java.io.RandomAccessFile
;
...
...
@@ -42,8 +44,7 @@ public final class FileDataSource implements DataSource {
try
{
file
=
new
RandomAccessFile
(
dataSpec
.
uri
.
getPath
(),
"r"
);
file
.
seek
(
dataSpec
.
position
);
bytesRemaining
=
dataSpec
.
length
==
DataSpec
.
LENGTH_UNBOUNDED
?
file
.
length
()
-
dataSpec
.
position
bytesRemaining
=
dataSpec
.
length
==
C
.
LENGTH_UNBOUNDED
?
file
.
length
()
-
dataSpec
.
position
:
dataSpec
.
length
;
return
bytesRemaining
;
}
catch
(
IOException
e
)
{
...
...
library/src/main/java/com/google/android/exoplayer/upstream/HttpDataSource.java
View file @
79c2f535
...
...
@@ -15,6 +15,7 @@
*/
package
com
.
google
.
android
.
exoplayer
.
upstream
;
import
com.google.android.exoplayer.C
;
import
com.google.android.exoplayer.util.Assertions
;
import
com.google.android.exoplayer.util.Predicate
;
import
com.google.android.exoplayer.util.Util
;
...
...
@@ -258,16 +259,9 @@ public class HttpDataSource implements DataSource {
}
long
contentLength
=
getContentLength
(
connection
);
dataLength
=
dataSpec
.
length
==
DataSpec
.
LENGTH_UNBOUNDED
?
contentLength
:
dataSpec
.
length
;
if
(
dataLength
==
DataSpec
.
LENGTH_UNBOUNDED
)
{
// The DataSpec specified unbounded length and we failed to resolve a length from the
// response headers.
throw
new
HttpDataSourceException
(
new
UnexpectedLengthException
(
DataSpec
.
LENGTH_UNBOUNDED
,
DataSpec
.
LENGTH_UNBOUNDED
),
dataSpec
);
}
dataLength
=
dataSpec
.
length
==
C
.
LENGTH_UNBOUNDED
?
contentLength
:
dataSpec
.
length
;
if
(
dataSpec
.
length
!=
DataSpec
.
LENGTH_UNBOUNDED
&&
contentLength
!=
DataSpec
.
LENGTH_UNBOUNDED
if
(
dataSpec
.
length
!=
C
.
LENGTH_UNBOUNDED
&&
contentLength
!=
C
.
LENGTH_UNBOUNDED
&&
contentLength
!=
dataSpec
.
length
)
{
// The DataSpec specified a length and we resolved a length from the response headers, but
// the two lengths do not match.
...
...
@@ -305,9 +299,9 @@ public class HttpDataSource implements DataSource {
if
(
listener
!=
null
)
{
listener
.
onBytesTransferred
(
read
);
}
}
else
if
(
dataLength
!=
bytesRead
)
{
}
else
if
(
dataLength
!=
C
.
LENGTH_UNBOUNDED
&&
dataLength
!=
bytesRead
)
{
// Check for cases where the server closed the connection having not sent the correct amount
// of data.
// of data.
We can only do this if we know the length of the data we were expecting.
throw
new
HttpDataSourceException
(
new
UnexpectedLengthException
(
dataLength
,
bytesRead
),
dataSpec
);
}
...
...
@@ -364,14 +358,15 @@ public class HttpDataSource implements DataSource {
}
/**
* Returns the number of bytes that are still to be read for the current {@link DataSpec}. This
* value is equivalent to {@code dataSpec.length - bytesRead()}, where dataSpec is the
* {@link DataSpec} that was passed to the most recent call of {@link #open(DataSpec)}.
* Returns the number of bytes that are still to be read for the current {@link DataSpec}.
* <p>
* If the total length of the data being read is known, then this length minus {@code bytesRead()}
* is returned. If the total length is unknown, {@link C#LENGTH_UNBOUNDED} is returned.
*
* @return The
number of bytes remaining
.
* @return The
remaining length, or {@link C#LENGTH_UNBOUNDED}
.
*/
protected
final
long
bytesRemaining
()
{
return
dataLength
-
bytesRead
;
return
dataLength
==
C
.
LENGTH_UNBOUNDED
?
dataLength
:
dataLength
-
bytesRead
;
}
private
HttpURLConnection
makeConnection
(
DataSpec
dataSpec
)
throws
IOException
{
...
...
@@ -394,14 +389,14 @@ public class HttpDataSource implements DataSource {
private
String
buildRangeHeader
(
DataSpec
dataSpec
)
{
String
rangeRequest
=
"bytes="
+
dataSpec
.
position
+
"-"
;
if
(
dataSpec
.
length
!=
DataSpec
.
LENGTH_UNBOUNDED
)
{
if
(
dataSpec
.
length
!=
C
.
LENGTH_UNBOUNDED
)
{
rangeRequest
+=
(
dataSpec
.
position
+
dataSpec
.
length
-
1
);
}
return
rangeRequest
;
}
private
long
getContentLength
(
HttpURLConnection
connection
)
{
long
contentLength
=
DataSpec
.
LENGTH_UNBOUNDED
;
long
contentLength
=
C
.
LENGTH_UNBOUNDED
;
String
contentLengthHeader
=
connection
.
getHeaderField
(
"Content-Length"
);
if
(!
TextUtils
.
isEmpty
(
contentLengthHeader
))
{
try
{
...
...
@@ -435,10 +430,6 @@ public class HttpDataSource implements DataSource {
}
}
}
if
(
contentLength
==
DataSpec
.
LENGTH_UNBOUNDED
)
{
Log
.
w
(
TAG
,
"Unable to parse content length ["
+
contentLengthHeader
+
"] ["
+
contentRangeHeader
+
"]"
);
}
return
contentLength
;
}
...
...
library/src/main/java/com/google/android/exoplayer/upstream/TeeDataSource.java
View file @
79c2f535
...
...
@@ -15,6 +15,7 @@
*/
package
com
.
google
.
android
.
exoplayer
.
upstream
;
import
com.google.android.exoplayer.C
;
import
com.google.android.exoplayer.util.Assertions
;
import
java.io.IOException
;
...
...
@@ -39,7 +40,7 @@ public final class TeeDataSource implements DataSource {
@Override
public
long
open
(
DataSpec
dataSpec
)
throws
IOException
{
long
dataLength
=
upstream
.
open
(
dataSpec
);
if
(
dataSpec
.
length
==
DataSpec
.
LENGTH_UNBOUNDED
)
{
if
(
dataSpec
.
length
==
C
.
LENGTH_UNBOUNDED
&&
dataLength
!=
C
.
LENGTH_UNBOUNDED
)
{
// Reconstruct dataSpec in order to provide the resolved length to the sink.
dataSpec
=
new
DataSpec
(
dataSpec
.
uri
,
dataSpec
.
absoluteStreamPosition
,
dataLength
,
dataSpec
.
key
,
dataSpec
.
position
,
dataSpec
.
uriIsFullStream
);
...
...
library/src/main/java/com/google/android/exoplayer/upstream/cache/CacheDataSink.java
View file @
79c2f535
...
...
@@ -15,6 +15,7 @@
*/
package
com
.
google
.
android
.
exoplayer
.
upstream
.
cache
;
import
com.google.android.exoplayer.C
;
import
com.google.android.exoplayer.upstream.DataSink
;
import
com.google.android.exoplayer.upstream.DataSpec
;
import
com.google.android.exoplayer.util.Assertions
;
...
...
@@ -63,6 +64,9 @@ public class CacheDataSink implements DataSink {
@Override
public
DataSink
open
(
DataSpec
dataSpec
)
throws
CacheDataSinkException
{
// TODO: Support caching for unbounded requests. See TODO in {@link CacheDataSource} for
// more details.
Assertions
.
checkState
(
dataSpec
.
length
!=
C
.
LENGTH_UNBOUNDED
);
try
{
this
.
dataSpec
=
dataSpec
;
dataSpecBytesWritten
=
0
;
...
...
library/src/main/java/com/google/android/exoplayer/upstream/cache/CacheDataSource.java
View file @
79c2f535
...
...
@@ -15,6 +15,7 @@
*/
package
com
.
google
.
android
.
exoplayer
.
upstream
.
cache
;
import
com.google.android.exoplayer.C
;
import
com.google.android.exoplayer.upstream.DataSink
;
import
com.google.android.exoplayer.upstream.DataSource
;
import
com.google.android.exoplayer.upstream.DataSpec
;
...
...
@@ -34,10 +35,26 @@ import java.io.IOException;
*/
public
final
class
CacheDataSource
implements
DataSource
{
/**
* Interface definition for a callback to be notified of {@link CacheDataSource} events.
*/
public
interface
EventListener
{
/**
* Invoked when bytes have been read from {@link #cache} since the last invocation.
*
* @param cacheSizeBytes Current cache size in bytes.
* @param cachedBytesRead Total bytes read from {@link #cache} since last report.
*/
void
onCachedBytesRead
(
long
cacheSizeBytes
,
long
cachedBytesRead
);
}
private
final
Cache
cache
;
private
final
DataSource
cacheReadDataSource
;
private
final
DataSource
cacheWriteDataSource
;
private
final
DataSource
upstreamDataSource
;
private
final
EventListener
eventListener
;
private
final
boolean
blockOnCache
;
private
final
boolean
ignoreCacheOnError
;
...
...
@@ -49,6 +66,7 @@ public final class CacheDataSource implements DataSource {
private
long
bytesRemaining
;
private
CacheSpan
lockedSpan
;
private
boolean
ignoreCache
;
private
long
totalCachedBytesRead
;
/**
* Constructs an instance with default {@link DataSource} and {@link DataSink} instances for
...
...
@@ -67,7 +85,7 @@ public final class CacheDataSource implements DataSource {
public
CacheDataSource
(
Cache
cache
,
DataSource
upstream
,
boolean
blockOnCache
,
boolean
ignoreCacheOnError
,
long
maxCacheFileSize
)
{
this
(
cache
,
upstream
,
new
FileDataSource
(),
new
CacheDataSink
(
cache
,
maxCacheFileSize
),
blockOnCache
,
ignoreCacheOnError
);
blockOnCache
,
ignoreCacheOnError
,
null
);
}
/**
...
...
@@ -84,9 +102,11 @@ public final class CacheDataSource implements DataSource {
* @param ignoreCacheOnError Whether the cache is bypassed following any cache related error. If
* true, then cache related exceptions may be thrown for one cycle of open, read and close
* calls. Subsequent cycles of these calls will then bypass the cache.
* @param eventListener An optional {@link EventListener} to receive events.
*/
public
CacheDataSource
(
Cache
cache
,
DataSource
upstream
,
DataSource
cacheReadDataSource
,
DataSink
cacheWriteDataSink
,
boolean
blockOnCache
,
boolean
ignoreCacheOnError
)
{
DataSink
cacheWriteDataSink
,
boolean
blockOnCache
,
boolean
ignoreCacheOnError
,
EventListener
eventListener
)
{
this
.
cache
=
cache
;
this
.
cacheReadDataSource
=
cacheReadDataSource
;
this
.
blockOnCache
=
blockOnCache
;
...
...
@@ -97,6 +117,7 @@ public final class CacheDataSource implements DataSource {
}
else
{
this
.
cacheWriteDataSource
=
null
;
}
this
.
eventListener
=
eventListener
;
}
@Override
...
...
@@ -104,7 +125,7 @@ public final class CacheDataSource implements DataSource {
Assertions
.
checkState
(
dataSpec
.
uriIsFullStream
);
// TODO: Support caching for unbounded requests. This requires storing the source length
// into the cache (the simplest approach is to incorporate it into each cache file's name).
Assertions
.
checkState
(
dataSpec
.
length
!=
DataSpec
.
LENGTH_UNBOUNDED
);
Assertions
.
checkState
(
dataSpec
.
length
!=
C
.
LENGTH_UNBOUNDED
);
try
{
uri
=
dataSpec
.
uri
;
key
=
dataSpec
.
key
;
...
...
@@ -121,10 +142,13 @@ public final class CacheDataSource implements DataSource {
@Override
public
int
read
(
byte
[]
buffer
,
int
offset
,
int
max
)
throws
IOException
{
try
{
int
num
=
currentDataSource
.
read
(
buffer
,
offset
,
max
);
if
(
num
>=
0
)
{
readPosition
+=
num
;
bytesRemaining
-=
num
;
int
bytesRead
=
currentDataSource
.
read
(
buffer
,
offset
,
max
);
if
(
bytesRead
>=
0
)
{
if
(
currentDataSource
==
cacheReadDataSource
)
{
totalCachedBytesRead
+=
bytesRead
;
}
readPosition
+=
bytesRead
;
bytesRemaining
-=
bytesRead
;
}
else
{
closeCurrentSource
();
if
(
bytesRemaining
>
0
)
{
...
...
@@ -132,7 +156,7 @@ public final class CacheDataSource implements DataSource {
return
read
(
buffer
,
offset
,
max
);
}
}
return
num
;
return
bytesRead
;
}
catch
(
IOException
e
)
{
handleBeforeThrow
(
e
);
throw
e
;
...
...
@@ -141,6 +165,7 @@ public final class CacheDataSource implements DataSource {
@Override
public
void
close
()
throws
IOException
{
notifyBytesRead
();
try
{
closeCurrentSource
();
}
catch
(
IOException
e
)
{
...
...
@@ -215,4 +240,11 @@ public final class CacheDataSource implements DataSource {
}
}
private
void
notifyBytesRead
()
{
if
(
eventListener
!=
null
&&
totalCachedBytesRead
>
0
)
{
eventListener
.
onCachedBytesRead
(
cache
.
getCacheSpace
(),
totalCachedBytesRead
);
totalCachedBytesRead
=
0
;
}
}
}
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