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
fd519016
authored
Nov 13, 2014
by
Oliver Woodman
Browse files
Options
_('Browse Files')
Download
Email Patches
Plain Diff
Big HLS update. Add start of adaptive support, but leave disabled for now.
parent
6c6ba900
Hide whitespace changes
Inline
Side-by-side
Showing
16 changed files
with
735 additions
and
683 deletions
demo/src/main/java/com/google/android/exoplayer/demo/full/player/HlsRendererBuilder.java
demo/src/main/java/com/google/android/exoplayer/demo/simple/HlsRendererBuilder.java
library/src/main/java/com/google/android/exoplayer/hls/BitArrayChunk.java
library/src/main/java/com/google/android/exoplayer/hls/HlsChunk.java
library/src/main/java/com/google/android/exoplayer/hls/HlsChunkSource.java
library/src/main/java/com/google/android/exoplayer/hls/HlsMasterPlaylist.java
library/src/main/java/com/google/android/exoplayer/hls/HlsMasterPlaylistParser.java
library/src/main/java/com/google/android/exoplayer/hls/HlsMediaPlaylistParser.java
library/src/main/java/com/google/android/exoplayer/hls/HlsParserUtil.java
library/src/main/java/com/google/android/exoplayer/hls/HlsSampleSource.java
library/src/main/java/com/google/android/exoplayer/hls/TsChunk.java
library/src/main/java/com/google/android/exoplayer/parser/ts/TsExtractor.java → library/src/main/java/com/google/android/exoplayer/hls/TsExtractor.java
library/src/main/java/com/google/android/exoplayer/hls/HlsChunkOperationHolder.java → library/src/main/java/com/google/android/exoplayer/hls/Variant.java
library/src/main/java/com/google/android/exoplayer/metadata/Id3Parser.java
library/src/main/java/com/google/android/exoplayer/metadata/MetadataParser.java
library/src/main/java/com/google/android/exoplayer/parser/ts/BitsArray.java → library/src/main/java/com/google/android/exoplayer/util/BitArray.java
demo/src/main/java/com/google/android/exoplayer/demo/full/player/HlsRendererBuilder.java
View file @
fd519016
...
...
@@ -15,8 +15,6 @@
*/
package
com
.
google
.
android
.
exoplayer
.
demo
.
full
.
player
;
import
com.google.android.exoplayer.DefaultLoadControl
;
import
com.google.android.exoplayer.LoadControl
;
import
com.google.android.exoplayer.MediaCodecAudioTrackRenderer
;
import
com.google.android.exoplayer.MediaCodecVideoTrackRenderer
;
import
com.google.android.exoplayer.TrackRenderer
;
...
...
@@ -25,13 +23,13 @@ import com.google.android.exoplayer.demo.full.player.DemoPlayer.RendererBuilder;
import
com.google.android.exoplayer.demo.full.player.DemoPlayer.RendererBuilderCallback
;
import
com.google.android.exoplayer.hls.HlsChunkSource
;
import
com.google.android.exoplayer.hls.HlsMasterPlaylist
;
import
com.google.android.exoplayer.hls.HlsMasterPlaylist.Variant
;
import
com.google.android.exoplayer.hls.HlsMasterPlaylistParser
;
import
com.google.android.exoplayer.hls.HlsSampleSource
;
import
com.google.android.exoplayer.hls.Variant
;
import
com.google.android.exoplayer.metadata.Id3Parser
;
import
com.google.android.exoplayer.metadata.MetadataTrackRenderer
;
import
com.google.android.exoplayer.upstream.BufferPool
;
import
com.google.android.exoplayer.upstream.DataSource
;
import
com.google.android.exoplayer.upstream.DefaultBandwidthMeter
;
import
com.google.android.exoplayer.upstream.UriDataSource
;
import
com.google.android.exoplayer.util.ManifestFetcher
;
import
com.google.android.exoplayer.util.ManifestFetcher.ManifestCallback
;
...
...
@@ -47,9 +45,6 @@ import java.util.Collections;
*/
public
class
HlsRendererBuilder
implements
RendererBuilder
,
ManifestCallback
<
HlsMasterPlaylist
>
{
private
static
final
int
BUFFER_SEGMENT_SIZE
=
64
*
1024
;
private
static
final
int
VIDEO_BUFFER_SEGMENTS
=
200
;
private
final
String
userAgent
;
private
final
String
url
;
private
final
String
contentId
;
...
...
@@ -89,12 +84,12 @@ public class HlsRendererBuilder implements RendererBuilder, ManifestCallback<Hls
@Override
public
void
onManifest
(
String
contentId
,
HlsMasterPlaylist
manifest
)
{
LoadControl
loadControl
=
new
DefaultLoadControl
(
new
BufferPool
(
BUFFER_SEGMENT_SIZE
)
);
DefaultBandwidthMeter
bandwidthMeter
=
new
DefaultBandwidthMeter
(
);
DataSource
dataSource
=
new
UriDataSource
(
userAgent
,
null
);
HlsChunkSource
chunkSource
=
new
HlsChunkSource
(
dataSource
,
manifest
);
HlsSampleSource
sampleSource
=
new
HlsSampleSource
(
chunkSource
,
loadControl
,
VIDEO_BUFFER_SEGMENTS
*
BUFFER_SEGMENT_SIZE
,
true
,
3
);
DataSource
dataSource
=
new
UriDataSource
(
userAgent
,
bandwidthMeter
);
HlsChunkSource
chunkSource
=
new
HlsChunkSource
(
dataSource
,
manifest
,
bandwidthMeter
,
null
,
false
);
HlsSampleSource
sampleSource
=
new
HlsSampleSource
(
chunkSource
,
true
,
3
);
MediaCodecVideoTrackRenderer
videoRenderer
=
new
MediaCodecVideoTrackRenderer
(
sampleSource
,
MediaCodec
.
VIDEO_SCALING_MODE_SCALE_TO_FIT
,
0
,
player
.
getMainHandler
(),
player
,
50
);
MediaCodecAudioTrackRenderer
audioRenderer
=
new
MediaCodecAudioTrackRenderer
(
sampleSource
);
...
...
@@ -111,7 +106,7 @@ public class HlsRendererBuilder implements RendererBuilder, ManifestCallback<Hls
private
HlsMasterPlaylist
newSimpleMasterPlaylist
(
String
mediaPlaylistUrl
)
{
return
new
HlsMasterPlaylist
(
Uri
.
parse
(
""
),
Collections
.
singletonList
(
new
Variant
(
mediaPlaylistUrl
,
0
,
null
,
-
1
,
-
1
)));
Collections
.
singletonList
(
new
Variant
(
0
,
mediaPlaylistUrl
,
0
,
null
,
-
1
,
-
1
)));
}
}
demo/src/main/java/com/google/android/exoplayer/demo/simple/HlsRendererBuilder.java
View file @
fd519016
...
...
@@ -15,8 +15,6 @@
*/
package
com
.
google
.
android
.
exoplayer
.
demo
.
simple
;
import
com.google.android.exoplayer.DefaultLoadControl
;
import
com.google.android.exoplayer.LoadControl
;
import
com.google.android.exoplayer.MediaCodecAudioTrackRenderer
;
import
com.google.android.exoplayer.MediaCodecVideoTrackRenderer
;
import
com.google.android.exoplayer.TrackRenderer
;
...
...
@@ -26,11 +24,11 @@ import com.google.android.exoplayer.demo.simple.SimplePlayerActivity.RendererBui
import
com.google.android.exoplayer.demo.simple.SimplePlayerActivity.RendererBuilderCallback
;
import
com.google.android.exoplayer.hls.HlsChunkSource
;
import
com.google.android.exoplayer.hls.HlsMasterPlaylist
;
import
com.google.android.exoplayer.hls.HlsMasterPlaylist.Variant
;
import
com.google.android.exoplayer.hls.HlsMasterPlaylistParser
;
import
com.google.android.exoplayer.hls.HlsSampleSource
;
import
com.google.android.exoplayer.
upstream.BufferPool
;
import
com.google.android.exoplayer.
hls.Variant
;
import
com.google.android.exoplayer.upstream.DataSource
;
import
com.google.android.exoplayer.upstream.DefaultBandwidthMeter
;
import
com.google.android.exoplayer.upstream.UriDataSource
;
import
com.google.android.exoplayer.util.ManifestFetcher
;
import
com.google.android.exoplayer.util.ManifestFetcher.ManifestCallback
;
...
...
@@ -47,9 +45,6 @@ import java.util.Collections;
/* package */
class
HlsRendererBuilder
implements
RendererBuilder
,
ManifestCallback
<
HlsMasterPlaylist
>
{
private
static
final
int
BUFFER_SEGMENT_SIZE
=
64
*
1024
;
private
static
final
int
VIDEO_BUFFER_SEGMENTS
=
200
;
private
final
SimplePlayerActivity
playerActivity
;
private
final
String
userAgent
;
private
final
String
url
;
...
...
@@ -90,12 +85,11 @@ import java.util.Collections;
@Override
public
void
onManifest
(
String
contentId
,
HlsMasterPlaylist
manifest
)
{
LoadControl
loadControl
=
new
DefaultLoadControl
(
new
BufferPool
(
BUFFER_SEGMENT_SIZE
));
DataSource
dataSource
=
new
UriDataSource
(
userAgent
,
null
);
HlsChunkSource
chunkSource
=
new
HlsChunkSource
(
dataSource
,
manifest
);
HlsSampleSource
sampleSource
=
new
HlsSampleSource
(
chunkSource
,
loadControl
,
VIDEO_BUFFER_SEGMENTS
*
BUFFER_SEGMENT_SIZE
,
true
,
2
);
DefaultBandwidthMeter
bandwidthMeter
=
new
DefaultBandwidthMeter
();
DataSource
dataSource
=
new
UriDataSource
(
userAgent
,
bandwidthMeter
);
HlsChunkSource
chunkSource
=
new
HlsChunkSource
(
dataSource
,
manifest
,
bandwidthMeter
,
null
,
false
);
HlsSampleSource
sampleSource
=
new
HlsSampleSource
(
chunkSource
,
true
,
2
);
MediaCodecVideoTrackRenderer
videoRenderer
=
new
MediaCodecVideoTrackRenderer
(
sampleSource
,
MediaCodec
.
VIDEO_SCALING_MODE_SCALE_TO_FIT
,
0
,
playerActivity
.
getMainHandler
(),
playerActivity
,
50
);
...
...
@@ -109,7 +103,7 @@ import java.util.Collections;
private
HlsMasterPlaylist
newSimpleMasterPlaylist
(
String
mediaPlaylistUrl
)
{
return
new
HlsMasterPlaylist
(
Uri
.
parse
(
""
),
Collections
.
singletonList
(
new
Variant
(
mediaPlaylistUrl
,
0
,
null
,
-
1
,
-
1
)));
Collections
.
singletonList
(
new
Variant
(
0
,
mediaPlaylistUrl
,
0
,
null
,
-
1
,
-
1
)));
}
}
library/src/main/java/com/google/android/exoplayer/hls/BitArrayChunk.java
0 → 100644
View file @
fd519016
/*
* 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
.
hls
;
import
com.google.android.exoplayer.upstream.DataSource
;
import
com.google.android.exoplayer.upstream.DataSpec
;
import
com.google.android.exoplayer.util.BitArray
;
import
java.io.IOException
;
/**
* An abstract base class for {@link HlsChunk} implementations where the data should be loaded into
* a {@link BitArray} and subsequently consumed.
*/
public
abstract
class
BitArrayChunk
extends
HlsChunk
{
private
static
final
int
READ_GRANULARITY
=
16
*
1024
;
private
final
BitArray
bitArray
;
private
volatile
boolean
loadFinished
;
private
volatile
boolean
loadCanceled
;
/**
* @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 == C.LENGTH_UNBOUNDED} then
* the length resolved by {@code dataSource.open(dataSpec)} must not exceed
* {@link Integer#MAX_VALUE}.
* @param bitArray The {@link BitArray} into which the data should be loaded.
*/
public
BitArrayChunk
(
DataSource
dataSource
,
DataSpec
dataSpec
,
BitArray
bitArray
)
{
super
(
dataSource
,
dataSpec
);
this
.
bitArray
=
bitArray
;
}
@Override
public
void
consume
()
throws
IOException
{
consume
(
bitArray
);
}
/**
* Invoked by {@link #consume()}. Implementations should override this method to consume the
* loaded data.
*
* @param bitArray The {@link BitArray} containing the loaded data.
* @throws IOException If an error occurs consuming the loaded data.
*/
protected
abstract
void
consume
(
BitArray
bitArray
)
throws
IOException
;
/**
* Whether the whole of the chunk has been loaded.
*
* @return True if the whole of the chunk has been loaded. False otherwise.
*/
@Override
public
boolean
isLoadFinished
()
{
return
loadFinished
;
}
// Loadable implementation
@Override
public
final
void
cancelLoad
()
{
loadCanceled
=
true
;
}
@Override
public
final
boolean
isLoadCanceled
()
{
return
loadCanceled
;
}
@Override
public
final
void
load
()
throws
IOException
,
InterruptedException
{
try
{
bitArray
.
reset
();
dataSource
.
open
(
dataSpec
);
int
bytesRead
=
0
;
while
(
bytesRead
!=
-
1
&&
!
loadCanceled
)
{
bytesRead
=
bitArray
.
append
(
dataSource
,
READ_GRANULARITY
);
}
loadFinished
=
!
loadCanceled
;
}
finally
{
dataSource
.
close
();
}
}
}
library/src/main/java/com/google/android/exoplayer/hls/HlsChunk.java
View file @
fd519016
...
...
@@ -15,38 +15,21 @@
*/
package
com
.
google
.
android
.
exoplayer
.
hls
;
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
;
import
com.google.android.exoplayer.upstream.DataSourceStream
;
import
com.google.android.exoplayer.upstream.DataSpec
;
import
com.google.android.exoplayer.upstream.Loader.Loadable
;
import
com.google.android.exoplayer.upstream.NonBlockingInputStream
;
import
com.google.android.exoplayer.util.Assertions
;
import
java.io.IOException
;
/**
* An abstract base class for {@link Loadable} implementations that load chunks of data required
* for the playback of streams.
* <p>
* TODO: Figure out whether this should merge with the chunk package, or whether the hls
* implementation is going to naturally diverge.
* for the playback of HLS streams.
*/
public
abstract
class
HlsChunk
implements
Loadable
{
/**
* The reason for a {@link HlsChunkSource} having generated this chunk. For reporting only.
* Possible values for this variable are defined by the specific {@link HlsChunkSource}
* implementations.
*/
public
final
int
trigger
;
private
final
DataSource
dataSource
;
private
final
DataSpec
dataSpec
;
private
DataSourceStream
dataSourceStream
;
protected
final
DataSource
dataSource
;
protected
final
DataSpec
dataSpec
;
/**
* @param dataSource The source from which the data should be loaded.
...
...
@@ -54,123 +37,15 @@ public abstract class HlsChunk implements Loadable {
* {@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 trigger See {@link #trigger}.
*/
public
HlsChunk
(
DataSource
dataSource
,
DataSpec
dataSpec
,
int
trigger
)
{
public
HlsChunk
(
DataSource
dataSource
,
DataSpec
dataSpec
)
{
Assertions
.
checkState
(
dataSpec
.
length
<=
Integer
.
MAX_VALUE
);
this
.
dataSource
=
Assertions
.
checkNotNull
(
dataSource
);
this
.
dataSpec
=
Assertions
.
checkNotNull
(
dataSpec
);
this
.
trigger
=
trigger
;
}
/**
* Initializes the {@link HlsChunk}.
*
* @param allocator An {@link Allocator} from which the {@link Allocation} needed to contain the
* data can be obtained.
*/
public
final
void
init
(
Allocator
allocator
)
{
Assertions
.
checkState
(
dataSourceStream
==
null
);
dataSourceStream
=
new
DataSourceStream
(
dataSource
,
dataSpec
,
allocator
);
}
/**
* Releases the {@link HlsChunk}, releasing any backing {@link Allocation}s.
*/
public
final
void
release
()
{
if
(
dataSourceStream
!=
null
)
{
dataSourceStream
.
close
();
dataSourceStream
=
null
;
}
}
/**
* Gets the length of the chunk in bytes.
*
* @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
();
}
/**
* Whether the whole of the data has been consumed.
*
* @return True if the whole of the data has been consumed. False otherwise.
*/
public
final
boolean
isReadFinished
()
{
return
dataSourceStream
.
isEndOfStream
();
}
/**
* Whether the whole of the chunk has been loaded.
*
* @return True if the whole of the chunk has been loaded. False otherwise.
*/
public
final
boolean
isLoadFinished
()
{
return
dataSourceStream
.
isLoadFinished
();
}
public
abstract
void
consume
()
throws
IOException
;
/**
* Gets the number of bytes that have been loaded.
*
* @return The number of bytes that have been loaded.
*/
public
final
long
bytesLoaded
()
{
return
dataSourceStream
.
getLoadPosition
();
}
/**
* Causes loaded data to be consumed.
*
* @throws IOException If an error occurs consuming the loaded data.
*/
public
final
void
consume
()
throws
IOException
{
Assertions
.
checkState
(
dataSourceStream
!=
null
);
consumeStream
(
dataSourceStream
);
}
/**
* Invoked by {@link #consume()}. Implementations may override this method if they wish to
* consume the loaded data at this point.
* <p>
* The default implementation is a no-op.
*
* @param stream The stream of loaded data.
* @throws IOException If an error occurs consuming the loaded data.
*/
protected
void
consumeStream
(
NonBlockingInputStream
stream
)
throws
IOException
{
// Do nothing.
}
protected
final
NonBlockingInputStream
getNonBlockingInputStream
()
{
return
dataSourceStream
;
}
protected
final
void
resetReadPosition
()
{
if
(
dataSourceStream
!=
null
)
{
dataSourceStream
.
resetReadPosition
();
}
else
{
// We haven't been initialized yet, so the read position must already be 0.
}
}
// Loadable implementation
@Override
public
final
void
cancelLoad
()
{
dataSourceStream
.
cancelLoad
();
}
@Override
public
final
boolean
isLoadCanceled
()
{
return
dataSourceStream
.
isLoadCanceled
();
}
@Override
public
final
void
load
()
throws
IOException
,
InterruptedException
{
dataSourceStream
.
load
();
}
public
abstract
boolean
isLoadFinished
();
}
library/src/main/java/com/google/android/exoplayer/hls/HlsChunkSource.java
View file @
fd519016
...
...
@@ -18,10 +18,12 @@ package com.google.android.exoplayer.hls;
import
com.google.android.exoplayer.C
;
import
com.google.android.exoplayer.MediaFormat
;
import
com.google.android.exoplayer.TrackRenderer
;
import
com.google.android.exoplayer.hls.TsExtractor.SamplePool
;
import
com.google.android.exoplayer.upstream.Aes128DataSource
;
import
com.google.android.exoplayer.upstream.BandwidthMeter
;
import
com.google.android.exoplayer.upstream.DataSource
;
import
com.google.android.exoplayer.upstream.DataSpec
;
import
com.google.android.exoplayer.u
pstream.NonBlockingInputStream
;
import
com.google.android.exoplayer.u
til.BitArray
;
import
com.google.android.exoplayer.util.Util
;
import
android.net.Uri
;
...
...
@@ -30,6 +32,8 @@ import android.os.SystemClock;
import
java.io.ByteArrayInputStream
;
import
java.io.IOException
;
import
java.math.BigInteger
;
import
java.util.ArrayList
;
import
java.util.Collections
;
import
java.util.List
;
import
java.util.Locale
;
...
...
@@ -41,26 +45,66 @@ import java.util.Locale;
*/
public
class
HlsChunkSource
{
private
static
final
float
BANDWIDTH_FRACTION
=
0.8f
;
private
static
final
long
MIN_BUFFER_TO_SWITCH_UP_US
=
5000000
;
private
static
final
long
MAX_BUFFER_TO_SWITCH_DOWN_US
=
15000000
;
private
final
SamplePool
samplePool
=
new
TsExtractor
.
SamplePool
();
private
final
DataSource
upstreamDataSource
;
private
final
HlsMasterPlaylist
masterPlaylist
;
private
final
HlsMediaPlaylistParser
mediaPlaylistParser
;
/* package */
HlsMediaPlaylist
mediaPlaylist
;
/* package */
boolean
mediaPlaylistWasLive
;
/* package */
long
lastMediaPlaylistLoadTimeMs
;
private
final
Variant
[]
enabledVariants
;
private
final
BandwidthMeter
bandwidthMeter
;
private
final
BitArray
bitArray
;
private
final
boolean
enableAdaptive
;
private
final
Uri
baseUri
;
private
final
int
maxWidth
;
private
final
int
maxHeight
;
/* package */
final
HlsMediaPlaylist
[]
mediaPlaylists
;
/* package */
final
long
[]
lastMediaPlaylistLoadTimesMs
;
/* package */
boolean
live
;
/* package */
long
durationUs
;
private
int
variantIndex
;
private
DataSource
encryptedDataSource
;
private
String
encryptionKeyUri
;
// TODO: Once proper m3u8 parsing is in place, actually use the url!
public
HlsChunkSource
(
DataSource
dataSource
,
HlsMasterPlaylist
masterPlaylist
)
{
/**
* @param dataSource A {@link DataSource} suitable for loading the media data.
* @param masterPlaylist The master playlist.
* @param variantIndices A subset of variant indices to consider, or null to consider all of the
* variants in the master playlist.
*/
public
HlsChunkSource
(
DataSource
dataSource
,
HlsMasterPlaylist
masterPlaylist
,
BandwidthMeter
bandwidthMeter
,
int
[]
variantIndices
,
boolean
enableAdaptive
)
{
this
.
upstreamDataSource
=
dataSource
;
this
.
masterPlaylist
=
masterPlaylist
;
this
.
bandwidthMeter
=
bandwidthMeter
;
this
.
enableAdaptive
=
enableAdaptive
;
baseUri
=
masterPlaylist
.
baseUri
;
bitArray
=
new
BitArray
();
mediaPlaylistParser
=
new
HlsMediaPlaylistParser
();
enabledVariants
=
filterVariants
(
masterPlaylist
,
variantIndices
);
lastMediaPlaylistLoadTimesMs
=
new
long
[
enabledVariants
.
length
];
mediaPlaylists
=
new
HlsMediaPlaylist
[
enabledVariants
.
length
];
int
maxWidth
=
-
1
;
int
maxHeight
=
-
1
;
// Select the first variant from the master playlist that's enabled.
long
minOriginalVariantIndex
=
Integer
.
MAX_VALUE
;
for
(
int
i
=
0
;
i
<
enabledVariants
.
length
;
i
++)
{
if
(
enabledVariants
[
i
].
index
<
minOriginalVariantIndex
)
{
minOriginalVariantIndex
=
enabledVariants
[
i
].
index
;
variantIndex
=
i
;
}
maxWidth
=
Math
.
max
(
enabledVariants
[
i
].
width
,
maxWidth
);
maxHeight
=
Math
.
max
(
enabledVariants
[
i
].
width
,
maxHeight
);
}
// TODO: We should allow the default values to be passed through the constructor.
this
.
maxWidth
=
maxWidth
>
0
?
maxWidth
:
1920
;
this
.
maxHeight
=
maxHeight
>
0
?
maxHeight
:
1080
;
}
public
long
getDurationUs
()
{
return
mediaPlaylistWasLive
?
TrackRenderer
.
UNKNOWN_TIME_US
:
mediaPlaylist
.
durationUs
;
return
live
?
TrackRenderer
.
UNKNOWN_TIME_US
:
durationUs
;
}
/**
...
...
@@ -72,49 +116,33 @@ public class HlsChunkSource {
* @param out The {@link MediaFormat} on which the maximum video dimensions should be set.
*/
public
void
getMaxVideoDimensions
(
MediaFormat
out
)
{
// TODO: Implement this.
out
.
setMaxVideoDimensions
(
maxWidth
,
maxHeight
);
}
/**
* Updates the provided {@link HlsChunkOperationHolder} to contain the next operation that should
* be performed by the calling {@link HlsSampleSource}.
* <p>
* The next operation comprises of a possibly shortened queue length (shortened if the
* implementation wishes for the caller to discard {@link TsChunk}s from the queue), together
* with the next {@link HlsChunk} to load. The next chunk may be a {@link TsChunk} to be added to
* the queue, or another {@link HlsChunk} type (e.g. to load initialization data), or null if the
* source is not able to provide a chunk in its current state.
* Returns the next {@link HlsChunk} that should be loaded.
*
* @param
queue A representation of the currently buffered {@link TsChunk}s
.
* @param seekPositionUs If the
queue is empty, this parameter must specify the seek position. If
*
the queue is non-empty
then this parameter is ignored.
* @param
previousTsChunk The previously loaded chunk that the next chunk should follow
.
* @param seekPositionUs If the
re is no previous chunk, this parameter must specify the seek
*
position. If there is a previous chunk
then this parameter is ignored.
* @param playbackPositionUs The current playback position.
* @param out A holder for the next operation, whose {@link HlsChunkOperationHolder#queueSize} is
* initially equal to the length of the queue, and whose {@linkHls ChunkOperationHolder#chunk}
* is initially equal to null or a {@link TsChunk} previously supplied by the
* {@link HlsChunkSource} that the caller has not yet finished loading. In the latter case the
* chunk can either be replaced or left unchanged. Note that leaving the chunk unchanged is
* both preferred and more efficient than replacing it with a new but identical chunk.
* @return The next chunk to load.
*/
public
void
getChunkOperation
(
List
<
TsChunk
>
queue
,
long
seekPositionUs
,
long
playbackPositionUs
,
HlsChunkOperationHolder
out
)
{
if
(
out
.
chunk
!=
null
)
{
// We already have a chunk. Keep it.
return
;
}
public
HlsChunk
getChunkOperation
(
TsChunk
previousTsChunk
,
long
seekPositionUs
,
long
playbackPositionUs
)
{
HlsMediaPlaylist
mediaPlaylist
=
mediaPlaylists
[
variantIndex
];
if
(
mediaPlaylist
==
null
)
{
out
.
chunk
=
newMediaPlaylistChunk
();
return
;
return
newMediaPlaylistChunk
();
}
int
chunkMediaSequence
=
0
;
if
(
mediaPlaylistWasL
ive
)
{
if
(
queue
.
isEmpty
()
)
{
if
(
l
ive
)
{
if
(
previousTsChunk
==
null
)
{
chunkMediaSequence
=
getLiveStartChunkMediaSequence
();
}
else
{
// For live nextChunkIndex contains chunk media sequence number.
chunkMediaSequence
=
queue
.
get
(
queue
.
size
()
-
1
)
.
nextChunkIndex
;
chunkMediaSequence
=
previousTsChunk
.
nextChunkIndex
;
// If the updated playlist is far ahead and doesn't even have the last chunk from the
// queue, then try to catch up, skip a few chunks and start as if it was a new playlist.
if
(
chunkMediaSequence
<
mediaPlaylist
.
mediaSequence
)
{
...
...
@@ -124,28 +152,26 @@ public class HlsChunkSource {
}
}
else
{
// Not live.
if
(
queue
.
isEmpty
()
)
{
if
(
previousTsChunk
==
null
)
{
chunkMediaSequence
=
Util
.
binarySearchFloor
(
mediaPlaylist
.
segments
,
seekPositionUs
,
true
,
true
)
+
mediaPlaylist
.
mediaSequence
;
}
else
{
chunkMediaSequence
=
queue
.
get
(
queue
.
size
()
-
1
)
.
nextChunkIndex
;
chunkMediaSequence
=
previousTsChunk
.
nextChunkIndex
;
}
}
if
(
chunkMediaSequence
==
-
1
)
{
out
.
chunk
=
null
;
return
;
// We've reached the end of the stream.
return
null
;
}
int
chunkIndex
=
chunkMediaSequence
-
mediaPlaylist
.
mediaSequence
;
// If the end of the playlist is reached.
if
(
chunkIndex
>=
mediaPlaylist
.
segments
.
size
())
{
if
(
mediaPlaylist
.
live
&&
shouldRerequestMediaPlaylist
())
{
out
.
chunk
=
newMediaPlaylistChunk
();
return
newMediaPlaylistChunk
();
}
else
{
out
.
chunk
=
null
;
return
null
;
}
return
;
}
HlsMediaPlaylist
.
Segment
segment
=
mediaPlaylist
.
segments
.
get
(
chunkIndex
);
...
...
@@ -156,97 +182,204 @@ public class HlsChunkSource {
if
(!
segment
.
encryptionKeyUri
.
equals
(
encryptionKeyUri
))
{
// Encryption is specified and the key has changed.
Uri
keyUri
=
Util
.
getMergedUri
(
mediaPlaylist
.
baseUri
,
segment
.
encryptionKeyUri
);
out
.
chunk
=
newEncryptionKeyChunk
(
keyUri
,
segment
.
encryptionIV
);
HlsChunk
toReturn
=
newEncryptionKeyChunk
(
keyUri
,
segment
.
encryptionIV
);
encryptionKeyUri
=
segment
.
encryptionKeyUri
;
return
;
return
toReturn
;
}
}
else
{
encryptedDataSource
=
null
;
encryptionKeyUri
=
null
;
}
DataSpec
dataSpec
=
new
DataSpec
(
chunkUri
,
0
,
C
.
LENGTH_UNBOUNDED
,
null
);
long
startTimeUs
;
boolean
splicingIn
=
previousTsChunk
!=
null
&&
previousTsChunk
.
splicingOut
;
int
nextChunkMediaSequence
=
chunkMediaSequence
+
1
;
if
(
mediaPlaylistWasL
ive
)
{
if
(
queue
.
isEmpty
()
)
{
if
(
l
ive
)
{
if
(
previousTsChunk
==
null
)
{
startTimeUs
=
0
;
}
else
if
(
splicingIn
)
{
startTimeUs
=
previousTsChunk
.
startTimeUs
;
}
else
{
startTimeUs
=
queue
.
get
(
queue
.
size
()
-
1
)
.
endTimeUs
;
startTimeUs
=
previousTsChunk
.
endTimeUs
;
}
}
else
{
// Not live.
startTimeUs
=
segment
.
startTimeUs
;
if
(
chunkIndex
==
mediaPlaylist
.
segments
.
size
()
-
1
)
{
nextChunkMediaSequence
=
-
1
;
}
}
if
(!
mediaPlaylist
.
live
&&
chunkIndex
==
mediaPlaylist
.
segments
.
size
()
-
1
)
{
nextChunkMediaSequence
=
-
1
;
}
long
endTimeUs
=
startTimeUs
+
(
long
)
(
segment
.
durationSecs
*
1000000
);
int
currentVariantIndex
=
variantIndex
;
boolean
splicingOut
=
false
;
if
(
splicingIn
)
{
// Do nothing.
}
else
if
(
enableAdaptive
&&
nextChunkMediaSequence
!=
-
1
)
{
int
idealVariantIndex
=
getVariantIndexForBandwdith
(
(
int
)
(
bandwidthMeter
.
getBitrateEstimate
()
*
BANDWIDTH_FRACTION
));
long
bufferedUs
=
startTimeUs
-
playbackPositionUs
;
if
((
idealVariantIndex
>
currentVariantIndex
&&
bufferedUs
<
MAX_BUFFER_TO_SWITCH_DOWN_US
)
||
(
idealVariantIndex
<
currentVariantIndex
&&
bufferedUs
>
MIN_BUFFER_TO_SWITCH_UP_US
))
{
variantIndex
=
idealVariantIndex
;
}
splicingOut
=
variantIndex
!=
currentVariantIndex
;
if
(
splicingOut
)
{
// If we're splicing out, we want to load the same chunk again next time, but for a
// different variant.
nextChunkMediaSequence
=
chunkMediaSequence
;
}
}
// Configure the datasource for loading the chunk.
DataSource
dataSource
;
if
(
encryptedDataSource
!=
null
)
{
dataSource
=
encryptedDataSource
;
}
else
{
dataSource
=
upstreamDataSource
;
}
out
.
chunk
=
new
TsChunk
(
dataSource
,
dataSpec
,
0
,
0
,
startTimeUs
,
endTimeUs
,
nextChunkMediaSequence
,
segment
.
discontinuity
,
false
);
DataSpec
dataSpec
=
new
DataSpec
(
chunkUri
,
0
,
C
.
LENGTH_UNBOUNDED
,
null
);
// Configure the extractor that will read the chunk.
TsExtractor
extractor
;
if
(
previousTsChunk
==
null
||
splicingIn
||
segment
.
discontinuity
)
{
extractor
=
new
TsExtractor
(
startTimeUs
,
samplePool
);
}
else
{
extractor
=
previousTsChunk
.
extractor
;
}
if
(
splicingOut
)
{
extractor
.
discardFromNextKeyframes
();
}
return
new
TsChunk
(
dataSource
,
dataSpec
,
extractor
,
enabledVariants
[
currentVariantIndex
].
index
,
startTimeUs
,
endTimeUs
,
nextChunkMediaSequence
,
splicingOut
);
}
private
int
getVariantIndexForBandwdith
(
int
bandwidth
)
{
for
(
int
i
=
0
;
i
<
enabledVariants
.
length
-
1
;
i
++)
{
if
(
enabledVariants
[
i
].
bandwidth
<=
bandwidth
)
{
return
i
;
}
}
return
enabledVariants
.
length
-
1
;
}
private
boolean
shouldRerequestMediaPlaylist
()
{
// Don't re-request media playlist more often than one-half of the target duration.
HlsMediaPlaylist
mediaPlaylist
=
mediaPlaylists
[
variantIndex
];
long
timeSinceLastMediaPlaylistLoadMs
=
SystemClock
.
elapsedRealtime
()
-
lastMediaPlaylistLoadTime
Ms
;
SystemClock
.
elapsedRealtime
()
-
lastMediaPlaylistLoadTime
sMs
[
variantIndex
]
;
return
timeSinceLastMediaPlaylistLoadMs
>=
(
mediaPlaylist
.
targetDurationSecs
*
1000
)
/
2
;
}
private
int
getLiveStartChunkMediaSequence
()
{
// For live start playback from the third chunk from the end.
HlsMediaPlaylist
mediaPlaylist
=
mediaPlaylists
[
variantIndex
];
int
chunkIndex
=
mediaPlaylist
.
segments
.
size
()
>
3
?
mediaPlaylist
.
segments
.
size
()
-
3
:
0
;
return
chunkIndex
+
mediaPlaylist
.
mediaSequence
;
}
private
MediaPlaylistChunk
newMediaPlaylistChunk
()
{
Uri
mediaPlaylistUri
=
Util
.
getMergedUri
(
masterPlaylist
.
baseUri
,
masterPlaylist
.
variants
.
get
(
0
).
url
);
Uri
mediaPlaylistUri
=
Util
.
getMergedUri
(
baseUri
,
enabledVariants
[
variantIndex
].
url
);
DataSpec
dataSpec
=
new
DataSpec
(
mediaPlaylistUri
,
0
,
C
.
LENGTH_UNBOUNDED
,
null
);
Uri
mediaPlaylistB
aseUri
=
Util
.
parseBaseUri
(
mediaPlaylistUri
.
toString
());
return
new
MediaPlaylistChunk
(
upstreamDataSource
,
dataSpec
,
0
,
mediaPlaylistB
aseUri
);
Uri
b
aseUri
=
Util
.
parseBaseUri
(
mediaPlaylistUri
.
toString
());
return
new
MediaPlaylistChunk
(
variantIndex
,
upstreamDataSource
,
dataSpec
,
b
aseUri
);
}
private
EncryptionKeyChunk
newEncryptionKeyChunk
(
Uri
keyUri
,
String
iv
)
{
DataSpec
dataSpec
=
new
DataSpec
(
keyUri
,
0
,
C
.
LENGTH_UNBOUNDED
,
null
);
return
new
EncryptionKeyChunk
(
upstreamDataSource
,
dataSpec
,
0
,
iv
);
return
new
EncryptionKeyChunk
(
upstreamDataSource
,
dataSpec
,
iv
);
}
private
static
Variant
[]
filterVariants
(
HlsMasterPlaylist
masterPlaylist
,
int
[]
variantIndices
)
{
List
<
Variant
>
masterVariants
=
masterPlaylist
.
variants
;
ArrayList
<
Variant
>
enabledVariants
=
new
ArrayList
<
Variant
>();
if
(
variantIndices
!=
null
)
{
for
(
int
i
=
0
;
i
<
variantIndices
.
length
;
i
++)
{
enabledVariants
.
add
(
masterVariants
.
get
(
variantIndices
[
i
]));
}
}
else
{
// If variantIndices is null then all variants are initially considered.
enabledVariants
.
addAll
(
masterVariants
);
}
ArrayList
<
Variant
>
definiteVideoVariants
=
new
ArrayList
<
Variant
>();
ArrayList
<
Variant
>
definiteAudioOnlyVariants
=
new
ArrayList
<
Variant
>();
for
(
int
i
=
0
;
i
<
enabledVariants
.
size
();
i
++)
{
Variant
variant
=
enabledVariants
.
get
(
i
);
if
(
variant
.
height
>
0
||
variantHasExplicitCodecWithPrefix
(
variant
,
"avc"
))
{
definiteVideoVariants
.
add
(
variant
);
}
else
if
(
variantHasExplicitCodecWithPrefix
(
variant
,
"mp4a"
))
{
definiteAudioOnlyVariants
.
add
(
variant
);
}
}
if
(!
definiteVideoVariants
.
isEmpty
())
{
// We've identified some variants as definitely containing video. Assume variants within the
// master playlist are marked consistently, and hence that we have the full set. Filter out
// any other variants, which are likely to be audio only.
enabledVariants
=
definiteVideoVariants
;
}
else
if
(
definiteAudioOnlyVariants
.
size
()
<
enabledVariants
.
size
())
{
// We've identified some variants, but not all, as being audio only. Filter them out to leave
// the remaining variants, which are likely to contain video.
enabledVariants
.
removeAll
(
definiteAudioOnlyVariants
);
}
else
{
// Leave the enabled variants unchanged. They're likely either all video or all audio.
}
Collections
.
sort
(
enabledVariants
,
new
Variant
.
DecreasingBandwidthComparator
());
Variant
[]
enabledVariantsArray
=
new
Variant
[
enabledVariants
.
size
()];
enabledVariants
.
toArray
(
enabledVariantsArray
);
return
enabledVariantsArray
;
}
private
static
boolean
variantHasExplicitCodecWithPrefix
(
Variant
variant
,
String
prefix
)
{
String
[]
codecs
=
variant
.
codecs
;
if
(
codecs
==
null
)
{
return
false
;
}
for
(
int
i
=
0
;
i
<
codecs
.
length
;
i
++)
{
if
(
codecs
[
i
].
startsWith
(
prefix
))
{
return
true
;
}
}
return
false
;
}
private
class
MediaPlaylistChunk
extends
Hls
Chunk
{
private
class
MediaPlaylistChunk
extends
BitArray
Chunk
{
private
final
Uri
baseUri
;
@SuppressWarnings
(
"hiding"
)
private
final
int
variantIndex
;
private
final
Uri
playlistBaseUri
;
public
MediaPlaylistChunk
(
DataSource
dataSource
,
DataSpec
dataSpec
,
int
trigger
,
Uri
baseUri
)
{
super
(
dataSource
,
dataSpec
,
trigger
);
this
.
baseUri
=
baseUri
;
public
MediaPlaylistChunk
(
int
variantIndex
,
DataSource
dataSource
,
DataSpec
dataSpec
,
Uri
playlistBaseUri
)
{
super
(
dataSource
,
dataSpec
,
bitArray
);
this
.
variantIndex
=
variantIndex
;
this
.
playlistBaseUri
=
playlistBaseUri
;
}
@Override
protected
void
consumeStream
(
NonBlockingInputStream
stream
)
throws
IOException
{
byte
[]
data
=
new
byte
[(
int
)
stream
.
getAvailableByteCount
()];
stream
.
read
(
data
,
0
,
data
.
length
);
lastMediaPlaylistLoadTimeMs
=
SystemClock
.
elapsedRealtime
();
mediaPlaylist
=
mediaPlaylistParser
.
parse
(
new
ByteArrayInputStream
(
data
),
null
,
null
,
baseUri
);
mediaPlaylistWasLive
|=
mediaPlaylist
.
live
;
protected
void
consume
(
BitArray
data
)
throws
IOException
{
HlsMediaPlaylist
mediaPlaylist
=
mediaPlaylistParser
.
parse
(
new
ByteArrayInputStream
(
data
.
getData
(),
0
,
data
.
bytesLeft
()),
null
,
null
,
playlistBaseUri
);
mediaPlaylists
[
variantIndex
]
=
mediaPlaylist
;
lastMediaPlaylistLoadTimesMs
[
variantIndex
]
=
SystemClock
.
elapsedRealtime
();
live
|=
mediaPlaylist
.
live
;
durationUs
=
mediaPlaylist
.
durationUs
;
}
}
private
class
EncryptionKeyChunk
extends
Hls
Chunk
{
private
class
EncryptionKeyChunk
extends
BitArray
Chunk
{
private
final
String
iv
;
public
EncryptionKeyChunk
(
DataSource
dataSource
,
DataSpec
dataSpec
,
int
trigger
,
String
iv
)
{
super
(
dataSource
,
dataSpec
,
trigger
);
public
EncryptionKeyChunk
(
DataSource
dataSource
,
DataSpec
dataSpec
,
String
iv
)
{
super
(
dataSource
,
dataSpec
,
bitArray
);
if
(
iv
.
toLowerCase
(
Locale
.
getDefault
()).
startsWith
(
"0x"
))
{
this
.
iv
=
iv
.
substring
(
2
);
}
else
{
...
...
@@ -255,9 +388,9 @@ public class HlsChunkSource {
}
@Override
protected
void
consume
Stream
(
NonBlockingInputStream
stream
)
throws
IOException
{
byte
[]
keyData
=
new
byte
[(
int
)
stream
.
getAvailableByteCoun
t
()];
stream
.
read
(
keyData
,
0
,
keyData
.
length
);
protected
void
consume
(
BitArray
data
)
throws
IOException
{
byte
[]
secretKey
=
new
byte
[
data
.
bytesLef
t
()];
data
.
readBytes
(
secretKey
,
0
,
secretKey
.
length
);
int
ivParsed
=
Integer
.
parseInt
(
iv
,
16
);
String
iv
=
String
.
format
(
"%032X"
,
ivParsed
);
...
...
@@ -267,7 +400,7 @@ public class HlsChunkSource {
System
.
arraycopy
(
ivData
,
0
,
ivDataWithPadding
,
ivDataWithPadding
.
length
-
ivData
.
length
,
ivData
.
length
);
encryptedDataSource
=
new
Aes128DataSource
(
keyData
,
ivDataWithPadding
,
upstreamDataSource
);
encryptedDataSource
=
new
Aes128DataSource
(
secretKey
,
ivDataWithPadding
,
upstreamDataSource
);
}
}
...
...
library/src/main/java/com/google/android/exoplayer/hls/HlsMasterPlaylist.java
View file @
fd519016
...
...
@@ -24,25 +24,6 @@ import java.util.List;
*/
public
final
class
HlsMasterPlaylist
{
/**
* Variant stream reference.
*/
public
static
final
class
Variant
{
public
final
int
bandwidth
;
public
final
String
url
;
public
final
String
[]
codecs
;
public
final
int
width
;
public
final
int
height
;
public
Variant
(
String
url
,
int
bandwidth
,
String
[]
codecs
,
int
width
,
int
height
)
{
this
.
bandwidth
=
bandwidth
;
this
.
url
=
url
;
this
.
codecs
=
codecs
;
this
.
width
=
width
;
this
.
height
=
height
;
}
}
public
final
Uri
baseUri
;
public
final
List
<
Variant
>
variants
;
...
...
library/src/main/java/com/google/android/exoplayer/hls/HlsMasterPlaylistParser.java
View file @
fd519016
...
...
@@ -15,7 +15,6 @@
*/
package
com
.
google
.
android
.
exoplayer
.
hls
;
import
com.google.android.exoplayer.hls.HlsMasterPlaylist.Variant
;
import
com.google.android.exoplayer.util.ManifestParser
;
import
android.net.Uri
;
...
...
@@ -61,6 +60,7 @@ public final class HlsMasterPlaylistParser implements ManifestParser<HlsMasterPl
String
[]
codecs
=
null
;
int
width
=
-
1
;
int
height
=
-
1
;
int
variantIndex
=
0
;
String
line
;
while
((
line
=
reader
.
readLine
())
!=
null
)
{
...
...
@@ -70,15 +70,14 @@ public final class HlsMasterPlaylistParser implements ManifestParser<HlsMasterPl
}
if
(
line
.
startsWith
(
STREAM_INF_TAG
))
{
bandwidth
=
HlsParserUtil
.
parseIntAttr
(
line
,
BANDWIDTH_ATTR_REGEX
,
BANDWIDTH_ATTR
);
String
codecsString
=
HlsParserUtil
.
parseOptionalStringAttr
(
line
,
CODECS_ATTR_REGEX
,
CODECS_ATTR
);
String
codecsString
=
HlsParserUtil
.
parseOptionalStringAttr
(
line
,
CODECS_ATTR_REGEX
);
if
(
codecsString
!=
null
)
{
codecs
=
codecsString
.
split
(
"(\\s*,\\s*)|(\\s*$)"
);
}
else
{
codecs
=
null
;
}
String
resolutionString
=
HlsParserUtil
.
parseOptionalStringAttr
(
line
,
RESOLUTION_ATTR_REGEX
,
RESOLUTION_ATTR
);
String
resolutionString
=
HlsParserUtil
.
parseOptionalStringAttr
(
line
,
RESOLUTION_ATTR
_REGEX
);
if
(
resolutionString
!=
null
)
{
String
[]
widthAndHeight
=
resolutionString
.
split
(
"x"
);
width
=
Integer
.
parseInt
(
widthAndHeight
[
0
]);
...
...
@@ -88,7 +87,7 @@ public final class HlsMasterPlaylistParser implements ManifestParser<HlsMasterPl
height
=
-
1
;
}
}
else
if
(!
line
.
startsWith
(
"#"
))
{
variants
.
add
(
new
Variant
(
line
,
bandwidth
,
codecs
,
width
,
height
));
variants
.
add
(
new
Variant
(
variantIndex
++,
line
,
bandwidth
,
codecs
,
width
,
height
));
bandwidth
=
0
;
codecs
=
null
;
width
=
-
1
;
...
...
library/src/main/java/com/google/android/exoplayer/hls/HlsMediaPlaylistParser.java
View file @
fd519016
...
...
@@ -114,8 +114,7 @@ public final class HlsMediaPlaylistParser implements ManifestParser<HlsMediaPlay
}
else
{
segmentEncryptionKeyUri
=
HlsParserUtil
.
parseStringAttr
(
line
,
URI_ATTR_REGEX
,
URI_ATTR
);
segmentEncryptionIV
=
HlsParserUtil
.
parseOptionalStringAttr
(
line
,
IV_ATTR_REGEX
,
IV_ATTR
);
segmentEncryptionIV
=
HlsParserUtil
.
parseOptionalStringAttr
(
line
,
IV_ATTR_REGEX
);
if
(
segmentEncryptionIV
==
null
)
{
segmentEncryptionIV
=
Integer
.
toHexString
(
segmentMediaSequence
);
}
...
...
library/src/main/java/com/google/android/exoplayer/hls/HlsParserUtil.java
View file @
fd519016
...
...
@@ -36,7 +36,7 @@ import java.util.regex.Pattern;
throw
new
ParserException
(
String
.
format
(
"Couldn't match %s tag in %s"
,
tag
,
line
));
}
public
static
String
parseOptionalStringAttr
(
String
line
,
Pattern
pattern
,
String
tag
)
{
public
static
String
parseOptionalStringAttr
(
String
line
,
Pattern
pattern
)
{
Matcher
matcher
=
pattern
.
matcher
(
line
);
if
(
matcher
.
find
()
&&
matcher
.
groupCount
()
==
1
)
{
return
matcher
.
group
(
1
);
...
...
library/src/main/java/com/google/android/exoplayer/hls/HlsSampleSource.java
View file @
fd519016
...
...
@@ -15,47 +15,31 @@
*/
package
com
.
google
.
android
.
exoplayer
.
hls
;
import
com.google.android.exoplayer.C
;
import
com.google.android.exoplayer.LoadControl
;
import
com.google.android.exoplayer.MediaFormat
;
import
com.google.android.exoplayer.MediaFormatHolder
;
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.parser.ts.TsExtractor
;
import
com.google.android.exoplayer.upstream.Loader
;
import
com.google.android.exoplayer.upstream.Loader.Loadable
;
import
com.google.android.exoplayer.upstream.NonBlockingInputStream
;
import
com.google.android.exoplayer.util.Assertions
;
import
android.os.SystemClock
;
import
java.io.IOException
;
import
java.util.Collections
;
import
java.util.Iterator
;
import
java.util.LinkedList
;
import
java.util.List
;
/**
* A {@link SampleSource} for HLS streams.
* <p>
* TODO: Figure out whether this should merge with the chunk package, or whether the hls
* implementation is going to naturally diverge.
*/
public
class
HlsSampleSource
implements
SampleSource
,
Loader
.
Callback
{
private
static
final
long
MAX_SAMPLE_INTERLEAVING_OFFSET_US
=
5
000000
;
private
static
final
long
BUFFER_DURATION_US
=
20
000000
;
private
static
final
int
NO_RESET_PENDING
=
-
1
;
private
final
TsExtractor
.
SamplePool
samplePool
;
private
final
LoadControl
loadControl
;
private
final
HlsChunkSource
chunkSource
;
private
final
HlsChunkOperationHolder
currentLoadableHolder
;
private
final
LinkedList
<
TsExtractor
>
extractors
;
private
final
LinkedList
<
TsChunk
>
mediaChunks
;
private
final
List
<
TsChunk
>
readOnlyHlsChunks
;
private
final
int
bufferSizeContribution
;
private
final
boolean
frameAccurateSeeking
;
private
int
remainingReleaseCount
;
...
...
@@ -70,7 +54,10 @@ public class HlsSampleSource implements SampleSource, Loader.Callback {
private
long
downstreamPositionUs
;
private
long
lastSeekPositionUs
;
private
long
pendingResetPositionUs
;
private
long
lastPerformedBufferOperation
;
private
TsChunk
previousTsLoadable
;
private
HlsChunk
currentLoadable
;
private
boolean
loadingFinished
;
private
Loader
loader
;
private
IOException
currentLoadableException
;
...
...
@@ -78,18 +65,12 @@ public class HlsSampleSource implements SampleSource, Loader.Callback {
private
int
currentLoadableExceptionCount
;
private
long
currentLoadableExceptionTimestamp
;
public
HlsSampleSource
(
HlsChunkSource
chunkSource
,
LoadControl
loadControl
,
int
bufferSizeContribution
,
boolean
frameAccurateSeeking
,
int
downstreamRendererCount
)
{
public
HlsSampleSource
(
HlsChunkSource
chunkSource
,
boolean
frameAccurateSeeking
,
int
downstreamRendererCount
)
{
this
.
chunkSource
=
chunkSource
;
this
.
loadControl
=
loadControl
;
this
.
bufferSizeContribution
=
bufferSizeContribution
;
this
.
frameAccurateSeeking
=
frameAccurateSeeking
;
this
.
remainingReleaseCount
=
downstreamRendererCount
;
samplePool
=
new
TsExtractor
.
SamplePool
();
extractors
=
new
LinkedList
<
TsExtractor
>();
currentLoadableHolder
=
new
HlsChunkOperationHolder
();
mediaChunks
=
new
LinkedList
<
TsChunk
>();
readOnlyHlsChunks
=
Collections
.
unmodifiableList
(
mediaChunks
);
}
@Override
...
...
@@ -99,13 +80,12 @@ public class HlsSampleSource implements SampleSource, Loader.Callback {
}
if
(
loader
==
null
)
{
loader
=
new
Loader
(
"Loader:HLS"
);
loadControl
.
register
(
this
,
bufferSizeContribution
);
}
continueBufferingInternal
();
if
(
extractors
.
isEmpty
())
{
return
false
;
}
TsExtractor
extractor
=
extractors
.
get
(
0
);
TsExtractor
extractor
=
extractors
.
get
First
(
);
if
(
extractor
.
isPrepared
())
{
trackCount
=
extractor
.
getTrackCount
();
trackEnabledStates
=
new
boolean
[
trackCount
];
...
...
@@ -156,8 +136,9 @@ public class HlsSampleSource implements SampleSource, Loader.Callback {
if
(
loader
.
isLoading
())
{
loader
.
cancelLoading
();
}
else
{
clearHlsChunk
s
();
discardExtractor
s
();
clearCurrentLoadable
();
previousTsLoadable
=
null
;
}
}
}
...
...
@@ -171,50 +152,15 @@ public class HlsSampleSource implements SampleSource, Loader.Callback {
}
private
boolean
continueBufferingInternal
()
throws
IOException
{
updateLoadControl
();
if
(
isPendingReset
())
{
maybeStartLoading
();
if
(
isPendingReset
()
||
extractors
.
isEmpty
()
)
{
return
false
;
}
TsChunk
mediaChunk
=
mediaChunks
.
getFirst
();
int
currentVariant
=
mediaChunk
.
variantIndex
;
TsExtractor
extractor
;
if
(
extractors
.
isEmpty
())
{
extractor
=
new
TsExtractor
(
mediaChunk
.
startTimeUs
,
samplePool
);
extractors
.
addLast
(
extractor
);
if
(
mediaChunk
.
discardFromFirstKeyframes
)
{
extractor
.
discardFromNextKeyframes
();
}
}
else
{
extractor
=
extractors
.
getLast
();
}
if
(
mediaChunk
.
isReadFinished
()
&&
mediaChunks
.
size
()
>
1
)
{
discardDownstreamHlsChunk
();
mediaChunk
=
mediaChunks
.
getFirst
();
if
(
mediaChunk
.
discontinuity
||
mediaChunk
.
variantIndex
!=
currentVariant
)
{
extractor
=
new
TsExtractor
(
mediaChunk
.
startTimeUs
,
samplePool
);
extractors
.
addLast
(
extractor
);
}
if
(
mediaChunk
.
discardFromFirstKeyframes
)
{
extractor
.
discardFromNextKeyframes
();
}
}
// Allow the extractor to consume from the current chunk.
NonBlockingInputStream
inputStream
=
mediaChunk
.
getNonBlockingInputStream
();
boolean
haveSufficientSamples
=
extractor
.
consumeUntil
(
inputStream
,
downstreamPositionUs
+
MAX_SAMPLE_INTERLEAVING_OFFSET_US
);
if
(!
haveSufficientSamples
)
{
// If we can't read any more, then we always say we have sufficient samples.
haveSufficientSamples
=
mediaChunk
.
isLastChunk
()
&&
mediaChunk
.
isReadFinished
();
}
if
(!
haveSufficientSamples
&&
currentLoadableException
!=
null
)
{
boolean
haveSamples
=
extractors
.
getFirst
().
hasSamples
();
if
(!
haveSamples
&&
currentLoadableException
!=
null
)
{
throw
currentLoadableException
;
}
return
haveS
ufficientS
amples
;
return
haveSamples
;
}
@Override
...
...
@@ -228,11 +174,7 @@ public class HlsSampleSource implements SampleSource, Loader.Callback {
return
DISCONTINUITY_READ
;
}
if
(
onlyReadDiscontinuity
||
isPendingReset
())
{
return
NOTHING_READ
;
}
if
(
extractors
.
isEmpty
())
{
if
(
onlyReadDiscontinuity
||
isPendingReset
()
||
extractors
.
isEmpty
())
{
return
NOTHING_READ
;
}
...
...
@@ -266,12 +208,7 @@ public class HlsSampleSource implements SampleSource, Loader.Callback {
return
SAMPLE_READ
;
}
TsChunk
mediaChunk
=
mediaChunks
.
getFirst
();
if
(
mediaChunk
.
isLastChunk
()
&&
mediaChunk
.
isReadFinished
())
{
return
END_OF_STREAM
;
}
return
NOTHING_READ
;
return
loadingFinished
?
END_OF_STREAM
:
NOTHING_READ
;
}
@Override
...
...
@@ -283,32 +220,10 @@ public class HlsSampleSource implements SampleSource, Loader.Callback {
if
(
pendingResetPositionUs
==
positionUs
)
{
return
;
}
for
(
int
i
=
0
;
i
<
pendingDiscontinuities
.
length
;
i
++)
{
pendingDiscontinuities
[
i
]
=
true
;
}
TsChunk
mediaChunk
=
getHlsChunk
(
positionUs
);
if
(
mediaChunk
==
null
)
{
restartFrom
(
positionUs
);
}
else
{
discardExtractors
();
discardDownstreamHlsChunks
(
mediaChunk
);
mediaChunk
.
resetReadPosition
();
updateLoadControl
();
}
}
private
TsChunk
getHlsChunk
(
long
positionUs
)
{
Iterator
<
TsChunk
>
mediaChunkIterator
=
mediaChunks
.
iterator
();
while
(
mediaChunkIterator
.
hasNext
())
{
TsChunk
mediaChunk
=
mediaChunkIterator
.
next
();
if
(
positionUs
<
mediaChunk
.
startTimeUs
)
{
return
null
;
}
else
if
(
mediaChunk
.
isLastChunk
()
||
positionUs
<
mediaChunk
.
endTimeUs
)
{
return
mediaChunk
;
}
}
return
null
;
restartFrom
(
positionUs
);
}
@Override
...
...
@@ -317,22 +232,10 @@ public class HlsSampleSource implements SampleSource, Loader.Callback {
Assertions
.
checkState
(
enabledTrackCount
>
0
);
if
(
isPendingReset
())
{
return
pendingResetPositionUs
;
}
TsChunk
mediaChunk
=
mediaChunks
.
getLast
();
HlsChunk
currentLoadable
=
currentLoadableHolder
.
chunk
;
if
(
currentLoadable
!=
null
&&
mediaChunk
==
currentLoadable
)
{
// Linearly interpolate partially-fetched chunk times.
long
chunkLength
=
mediaChunk
.
getLength
();
if
(
chunkLength
!=
C
.
LENGTH_UNBOUNDED
)
{
return
mediaChunk
.
startTimeUs
+
((
mediaChunk
.
endTimeUs
-
mediaChunk
.
startTimeUs
)
*
mediaChunk
.
bytesLoaded
())
/
chunkLength
;
}
else
{
return
mediaChunk
.
startTimeUs
;
}
}
else
if
(
mediaChunk
.
isLastChunk
())
{
}
else
if
(
loadingFinished
)
{
return
TrackRenderer
.
END_OF_TRACK_US
;
}
else
{
return
mediaChunk
.
endTimeUs
;
return
extractors
.
getLast
().
getLargestSampleTimestamp
()
;
}
}
...
...
@@ -340,7 +243,6 @@ public class HlsSampleSource implements SampleSource, Loader.Callback {
public
void
release
()
{
Assertions
.
checkState
(
remainingReleaseCount
>
0
);
if
(--
remainingReleaseCount
==
0
&&
loader
!=
null
)
{
loadControl
.
unregister
(
this
);
loader
.
release
();
loader
=
null
;
}
...
...
@@ -348,7 +250,6 @@ public class HlsSampleSource implements SampleSource, Loader.Callback {
@Override
public
void
onLoadCompleted
(
Loadable
loadable
)
{
HlsChunk
currentLoadable
=
currentLoadableHolder
.
chunk
;
try
{
currentLoadable
.
consume
();
}
catch
(
IOException
e
)
{
...
...
@@ -357,28 +258,24 @@ public class HlsSampleSource implements SampleSource, Loader.Callback {
currentLoadableExceptionTimestamp
=
SystemClock
.
elapsedRealtime
();
currentLoadableExceptionFatal
=
true
;
}
finally
{
if
(!
isTsChunk
(
currentLoadable
))
{
currentLoadable
.
release
();
if
(
isTsChunk
(
currentLoadable
))
{
TsChunk
tsChunk
=
(
TsChunk
)
loadable
;
loadingFinished
=
tsChunk
.
isLastChunk
();
}
if
(!
currentLoadableExceptionFatal
)
{
clearCurrentLoadable
();
}
updateLoadControl
();
maybeStartLoading
();
}
}
@Override
public
void
onLoadCanceled
(
Loadable
loadable
)
{
HlsChunk
currentLoadable
=
currentLoadableHolder
.
chunk
;
if
(!
isTsChunk
(
currentLoadable
))
{
currentLoadable
.
release
();
}
clearCurrentLoadable
();
if
(
enabledTrackCount
>
0
)
{
restartFrom
(
pendingResetPositionUs
);
}
else
{
clearHlsChunks
();
loadControl
.
trimAllocator
();
previousTsLoadable
=
null
;
}
}
...
...
@@ -387,142 +284,65 @@ public class HlsSampleSource implements SampleSource, Loader.Callback {
currentLoadableException
=
e
;
currentLoadableExceptionCount
++;
currentLoadableExceptionTimestamp
=
SystemClock
.
elapsedRealtime
();
updateLoadControl
();
maybeStartLoading
();
}
private
void
restartFrom
(
long
positionUs
)
{
pendingResetPositionUs
=
positionUs
;
previousTsLoadable
=
null
;
loadingFinished
=
false
;
discardExtractors
();
if
(
loader
.
isLoading
())
{
loader
.
cancelLoading
();
}
else
{
clearHlsChunks
();
clearCurrentLoadable
();
updateLoadControl
();
maybeStartLoading
();
}
}
private
void
clearHlsChunks
()
{
discardDownstreamHlsChunks
(
null
);
}
private
void
clearCurrentLoadable
()
{
currentLoadable
Holder
.
chunk
=
null
;
currentLoadable
=
null
;
currentLoadableException
=
null
;
currentLoadableExceptionCount
=
0
;
currentLoadableExceptionFatal
=
false
;
}
private
void
updateLoadControl
()
{
if
(
currentLoadableExceptionFatal
)
{
// We've failed, but we still need to update the control with our current state.
loadControl
.
update
(
this
,
downstreamPositionUs
,
-
1
,
false
,
true
);
private
void
maybeStartLoading
()
{
if
(
currentLoadableExceptionFatal
||
loadingFinished
)
{
return
;
}
long
loadPositionUs
;
if
(
isPendingReset
())
{
loadPositionUs
=
pendingResetPositionUs
;
}
else
{
TsChunk
lastHlsChunk
=
mediaChunks
.
getLast
();
loadPositionUs
=
lastHlsChunk
.
nextChunkIndex
==
-
1
?
-
1
:
lastHlsChunk
.
endTimeUs
;
}
boolean
isBackedOff
=
currentLoadableException
!=
null
;
boolean
nextLoader
=
loadControl
.
update
(
this
,
downstreamPositionUs
,
loadPositionUs
,
isBackedOff
||
loader
.
isLoading
(),
false
);
long
now
=
SystemClock
.
elapsedRealtime
();
if
(
isBackedOff
)
{
long
elapsedMillis
=
now
-
currentLoadableExceptionTimestamp
;
long
elapsedMillis
=
SystemClock
.
elapsedRealtime
()
-
currentLoadableExceptionTimestamp
;
if
(
elapsedMillis
>=
getRetryDelayMillis
(
currentLoadableExceptionCount
))
{
resumeFromBackOff
();
currentLoadableException
=
null
;
loader
.
startLoading
(
currentLoadable
,
this
);
}
return
;
}
if
(!
loader
.
isLoading
())
{
if
(
currentLoadableHolder
.
chunk
==
null
||
now
-
lastPerformedBufferOperation
>
1000
)
{
lastPerformedBufferOperation
=
now
;
currentLoadableHolder
.
queueSize
=
readOnlyHlsChunks
.
size
();
chunkSource
.
getChunkOperation
(
readOnlyHlsChunks
,
pendingResetPositionUs
,
downstreamPositionUs
,
currentLoadableHolder
);
discardUpstreamHlsChunks
(
currentLoadableHolder
.
queueSize
);
}
if
(
nextLoader
)
{
maybeStartLoading
();
}
}
}
/**
* Resumes loading.
* <p>
* If the {@link HlsChunkSource} returns a chunk equivalent to the backed off chunk B, then the
* loading of B will be resumed. In all other cases B will be discarded and the new chunk will
* be loaded.
*/
private
void
resumeFromBackOff
()
{
currentLoadableException
=
null
;
HlsChunk
backedOffChunk
=
currentLoadableHolder
.
chunk
;
if
(!
isTsChunk
(
backedOffChunk
))
{
currentLoadableHolder
.
queueSize
=
readOnlyHlsChunks
.
size
();
chunkSource
.
getChunkOperation
(
readOnlyHlsChunks
,
pendingResetPositionUs
,
downstreamPositionUs
,
currentLoadableHolder
);
discardUpstreamHlsChunks
(
currentLoadableHolder
.
queueSize
);
if
(
currentLoadableHolder
.
chunk
==
backedOffChunk
)
{
// HlsChunk was unchanged. Resume loading.
loader
.
startLoading
(
backedOffChunk
,
this
);
}
else
{
backedOffChunk
.
release
();
maybeStartLoading
();
}
boolean
bufferFull
=
!
extractors
.
isEmpty
()
&&
(
extractors
.
getLast
().
getLargestSampleTimestamp
()
-
downstreamPositionUs
)
>=
BUFFER_DURATION_US
;
if
(
loader
.
isLoading
()
||
bufferFull
)
{
return
;
}
if
(
backedOffChunk
==
mediaChunks
.
getFirst
())
{
// We're not able to clear the first media chunk, so we have no choice but to continue
// loading it.
loader
.
startLoading
(
backedOffChunk
,
this
);
HlsChunk
nextLoadable
=
chunkSource
.
getChunkOperation
(
previousTsLoadable
,
pendingResetPositionUs
,
downstreamPositionUs
);
if
(
nextLoadable
==
null
)
{
return
;
}
// The current loadable is the last media chunk. Remove it before we invoke the chunk source,
// and add it back again afterwards.
TsChunk
removedChunk
=
mediaChunks
.
removeLast
();
Assertions
.
checkState
(
backedOffChunk
==
removedChunk
);
currentLoadableHolder
.
queueSize
=
readOnlyHlsChunks
.
size
();
chunkSource
.
getChunkOperation
(
readOnlyHlsChunks
,
pendingResetPositionUs
,
downstreamPositionUs
,
currentLoadableHolder
);
mediaChunks
.
add
(
removedChunk
);
if
(
currentLoadableHolder
.
chunk
==
backedOffChunk
)
{
// HlsChunk was unchanged. Resume loading.
loader
.
startLoading
(
backedOffChunk
,
this
);
}
else
{
// This call will remove and release at least one chunk from the end of mediaChunks. Since
// the current loadable is the last media chunk, it is guaranteed to be removed.
discardUpstreamHlsChunks
(
currentLoadableHolder
.
queueSize
);
clearCurrentLoadable
();
maybeStartLoading
();
}
}
private
void
maybeStartLoading
()
{
HlsChunk
currentLoadable
=
currentLoadableHolder
.
chunk
;
if
(
currentLoadable
==
null
)
{
// Nothing to load.
return
;
}
currentLoadable
.
init
(
loadControl
.
getAllocator
());
currentLoadable
=
nextLoadable
;
if
(
isTsChunk
(
currentLoadable
))
{
TsChunk
mediaChunk
=
(
TsChunk
)
currentLoadable
;
mediaChunks
.
add
(
mediaChunk
);
previousTsLoadable
=
(
TsChunk
)
currentLoadable
;
if
(
isPendingReset
())
{
discardExtractors
();
pendingResetPositionUs
=
NO_RESET_PENDING
;
}
if
(
extractors
.
isEmpty
()
||
extractors
.
getLast
()
!=
previousTsLoadable
.
extractor
)
{
extractors
.
addLast
(
previousTsLoadable
.
extractor
);
}
}
loader
.
startLoading
(
currentLoadable
,
this
);
}
...
...
@@ -534,39 +354,6 @@ public class HlsSampleSource implements SampleSource, Loader.Callback {
extractors
.
clear
();
}
/**
* Discards downstream media chunks until {@code untilChunk} if found. {@code untilChunk} is not
* itself discarded. Null can be passed to discard all media chunks.
*
* @param untilChunk The first media chunk to keep, or null to discard all media chunks.
*/
private
void
discardDownstreamHlsChunks
(
TsChunk
untilChunk
)
{
if
(
mediaChunks
.
isEmpty
()
||
untilChunk
==
mediaChunks
.
getFirst
())
{
return
;
}
while
(!
mediaChunks
.
isEmpty
()
&&
untilChunk
!=
mediaChunks
.
getFirst
())
{
mediaChunks
.
removeFirst
().
release
();
}
}
/**
* Discards the first downstream media chunk.
*/
private
void
discardDownstreamHlsChunk
()
{
mediaChunks
.
removeFirst
().
release
();
}
/**
* Discard upstream media chunks until the queue length is equal to the length specified.
*
* @param queueLength The desired length of the queue.
*/
private
void
discardUpstreamHlsChunks
(
int
queueLength
)
{
while
(
mediaChunks
.
size
()
>
queueLength
)
{
mediaChunks
.
removeLast
().
release
();
}
}
private
boolean
isTsChunk
(
HlsChunk
chunk
)
{
return
chunk
instanceof
TsChunk
;
}
...
...
library/src/main/java/com/google/android/exoplayer/hls/TsChunk.java
View file @
fd519016
...
...
@@ -15,9 +15,12 @@
*/
package
com
.
google
.
android
.
exoplayer
.
hls
;
import
com.google.android.exoplayer.C
;
import
com.google.android.exoplayer.upstream.DataSource
;
import
com.google.android.exoplayer.upstream.DataSpec
;
import
java.io.IOException
;
/**
* A MPEG2TS chunk.
*/
...
...
@@ -40,40 +43,87 @@ public final class TsChunk extends HlsChunk {
*/
public
final
int
nextChunkIndex
;
/**
* The encoding discontinuity indicator.
* True if this is the final chunk being loaded for the current variant, as we splice to another
* one. False otherwise.
*/
public
final
boolean
discontinuity
;
public
final
boolean
splicingOut
;
/**
*
For each track, whether samples from the first keyframe (inclusive) should be discard
ed.
*
The extractor into which this chunk is being consum
ed.
*/
public
final
boolean
discardFromFirstKeyframes
;
public
final
TsExtractor
extractor
;
private
volatile
int
loadPosition
;
private
volatile
boolean
loadFinished
;
private
volatile
boolean
loadCanceled
;
/**
* @param dataSource A {@link DataSource} for loading the data.
* @param dataSpec Defines the data to be loaded.
* @param trigger The reason for this chunk being selected.
* @param variantIndex The index of the variant in the master playlist.
* @param startTimeUs The start time of the media contained by the chunk, in microseconds.
* @param endTimeUs The end time of the media contained by the chunk, in microseconds.
* @param nextChunkIndex The index of the next chunk, or -1 if this is the last chunk.
* @param discontinuity The encoding discontinuity indicator.
* @param discardFromFirstKeyframes For each contained media stream, whether samples from the
* first keyframe (inclusive) should be discarded.
* @param splicingOut True if this is the final chunk being loaded for the current variant, as we
* splice to another one. False otherwise.
*/
public
TsChunk
(
DataSource
dataSource
,
DataSpec
dataSpec
,
int
trigger
,
int
variantIndex
,
long
startTimeUs
,
long
endTimeUs
,
int
nextChunkIndex
,
boolean
discontinuity
,
boolean
discardFromFirstKeyframes
)
{
super
(
dataSource
,
dataSpec
,
trigger
)
;
public
TsChunk
(
DataSource
dataSource
,
DataSpec
dataSpec
,
TsExtractor
tsExtractor
,
int
variantIndex
,
long
startTimeUs
,
long
endTimeUs
,
int
nextChunkIndex
,
boolean
splicingOut
)
{
super
(
dataSource
,
dataSpec
);
this
.
extractor
=
tsExtractor
;
this
.
variantIndex
=
variantIndex
;
this
.
startTimeUs
=
startTimeUs
;
this
.
endTimeUs
=
endTimeUs
;
this
.
nextChunkIndex
=
nextChunkIndex
;
this
.
discontinuity
=
discontinuity
;
this
.
discardFromFirstKeyframes
=
discardFromFirstKeyframes
;
this
.
splicingOut
=
splicingOut
;
}
@Override
public
void
consume
()
throws
IOException
{
// Do nothing.
}
public
boolean
isLastChunk
()
{
return
nextChunkIndex
==
-
1
;
}
@Override
public
boolean
isLoadFinished
()
{
return
loadFinished
;
}
// Loadable implementation
@Override
public
void
cancelLoad
()
{
loadCanceled
=
true
;
}
@Override
public
boolean
isLoadCanceled
()
{
return
loadCanceled
;
}
@Override
public
void
load
()
throws
IOException
,
InterruptedException
{
DataSpec
loadDataSpec
;
if
(
loadPosition
==
0
)
{
loadDataSpec
=
dataSpec
;
}
else
{
long
remainingLength
=
dataSpec
.
length
!=
C
.
LENGTH_UNBOUNDED
?
dataSpec
.
length
-
loadPosition
:
C
.
LENGTH_UNBOUNDED
;
loadDataSpec
=
new
DataSpec
(
dataSpec
.
uri
,
dataSpec
.
position
+
loadPosition
,
remainingLength
,
dataSpec
.
key
);
}
try
{
dataSource
.
open
(
loadDataSpec
);
int
bytesRead
=
0
;
while
(
bytesRead
!=
-
1
&&
!
loadCanceled
)
{
bytesRead
=
extractor
.
read
(
dataSource
);
}
loadFinished
=
!
loadCanceled
;
}
finally
{
dataSource
.
close
();
}
}
}
library/src/main/java/com/google/android/exoplayer/
parser/t
s/TsExtractor.java
→
library/src/main/java/com/google/android/exoplayer/
hl
s/TsExtractor.java
View file @
fd519016
...
...
@@ -13,25 +13,27 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package
com
.
google
.
android
.
exoplayer
.
parser
.
t
s
;
package
com
.
google
.
android
.
exoplayer
.
hl
s
;
import
com.google.android.exoplayer.MediaFormat
;
import
com.google.android.exoplayer.SampleHolder
;
import
com.google.android.exoplayer.upstream.
NonBlockingInputStream
;
import
com.google.android.exoplayer.upstream.
DataSource
;
import
com.google.android.exoplayer.util.Assertions
;
import
com.google.android.exoplayer.util.BitArray
;
import
com.google.android.exoplayer.util.CodecSpecificDataUtil
;
import
com.google.android.exoplayer.util.MimeTypes
;
import
android.annotation.SuppressLint
;
import
android.media.MediaCodec
;
import
android.media.MediaExtractor
;
import
android.util.Log
;
import
android.util.Pair
;
import
android.util.SparseArray
;
import
java.io.IOException
;
import
java.util.ArrayList
;
import
java.util.Collections
;
import
java.util.LinkedList
;
import
java.util.List
;
import
java.util.Queue
;
/**
...
...
@@ -49,15 +51,15 @@ public final class TsExtractor {
private
static
final
int
TS_STREAM_TYPE_H264
=
0x1B
;
private
static
final
int
TS_STREAM_TYPE_ID3
=
0x15
;
private
final
Bit
s
Array
tsPacketBuffer
;
private
final
BitArray
tsPacketBuffer
;
private
final
SparseArray
<
PesPayloadReader
>
pesPayloadReaders
;
// Indexed by streamType
private
final
SparseArray
<
TsPayloadReader
>
tsPayloadReaders
;
// Indexed by pid
private
final
SamplePool
samplePool
;
/* package */
final
long
firstSampleTimestamp
;
private
boolean
prepared
;
/* package */
boolean
pendingFirstSampleTimestampAdjustment
;
/* package */
long
firstSampleTimestamp
;
/* package */
long
sampleTimestampOffsetUs
;
/* package */
long
largestParsedTimestampUs
;
/* package */
boolean
discardFromNextKeyframes
;
...
...
@@ -66,7 +68,7 @@ public final class TsExtractor {
this
.
firstSampleTimestamp
=
firstSampleTimestamp
;
this
.
samplePool
=
samplePool
;
pendingFirstSampleTimestampAdjustment
=
true
;
tsPacketBuffer
=
new
Bit
s
Array
();
tsPacketBuffer
=
new
BitArray
();
pesPayloadReaders
=
new
SparseArray
<
PesPayloadReader
>();
tsPayloadReaders
=
new
SparseArray
<
TsPayloadReader
>();
tsPayloadReaders
.
put
(
TS_PAT_PID
,
new
PatReader
());
...
...
@@ -117,31 +119,19 @@ public final class TsExtractor {
}
/**
* For each track,
whether to discard
samples from the next keyframe (inclusive).
* For each track,
discards
samples from the next keyframe (inclusive).
*/
public
void
discardFromNextKeyframes
()
{
discardFromNextKeyframes
=
true
;
}
/**
* Consumes data from a {@link NonBlockingInputStream}.
* <p>
* The read terminates if the end of the input stream is reached, if insufficient data is
* available to read a sample, or if the extractor has consumed up to the specified target
* timestamp.
* Gets the largest timestamp of any sample parsed by the extractor.
*
* @param inputStream The input stream from which data should be read.
* @param targetTimestampUs A target timestamp to consume up to.
* @return True if the target timestamp was reached. False otherwise.
* @return The largest timestamp, or {@link Long#MIN_VALUE} if no samples have been parsed.
*/
public
boolean
consumeUntil
(
NonBlockingInputStream
inputStream
,
long
targetTimestampUs
)
{
while
(
largestParsedTimestampUs
<
targetTimestampUs
&&
readTSPacket
(
inputStream
)
!=
-
1
)
{
// Carry on.
}
if
(!
prepared
)
{
prepared
=
checkPrepared
();
}
return
largestParsedTimestampUs
>=
targetTimestampUs
;
public
long
getLargestSampleTimestamp
()
{
return
largestParsedTimestampUs
;
}
/**
...
...
@@ -204,23 +194,29 @@ public final class TsExtractor {
}
/**
* Read a single TS packet.
* Reads up to a single TS packet.
*
* @param dataSource The {@link DataSource} from which to read.
* @throws IOException If an error occurred reading from the source.
* @return The number of bytes read from the source.
*/
p
rivate
int
readTSPacket
(
NonBlockingInputStream
inputStream
)
{
// Read entire single TS packet.
if
(
inputStream
.
getAvailableByteCount
()
<
TS_PACKET_SIZE
)
{
p
ublic
int
read
(
DataSource
dataSource
)
throws
IOException
{
int
read
=
tsPacketBuffer
.
append
(
dataSource
,
TS_PACKET_SIZE
-
tsPacketBuffer
.
bytesLeft
());
if
(
read
==
-
1
)
{
return
-
1
;
}
tsPacketBuffer
.
reset
();
tsPacketBuffer
.
append
(
inputStream
,
TS_PACKET_SIZE
);
if
(
tsPacketBuffer
.
bytesLeft
()
!=
TS_PACKET_SIZE
)
{
return
read
;
}
// Parse TS header.
// Check sync byte.
int
syncByte
=
tsPacketBuffer
.
readUnsignedByte
();
if
(
syncByte
!=
TS_SYNC_BYTE
)
{
return
0
;
return
read
;
}
// Skip transportErrorIndicator.
tsPacketBuffer
.
skipBits
(
1
);
boolean
payloadUnitStartIndicator
=
tsPacketBuffer
.
readBit
();
...
...
@@ -243,12 +239,17 @@ public final class TsExtractor {
// Read Payload.
if
(
payloadExists
)
{
TsPayloadReader
payloadReader
=
tsPayloadReaders
.
get
(
pid
);
if
(
payloadReader
=
=
null
)
{
return
0
;
if
(
payloadReader
!
=
null
)
{
payloadReader
.
read
(
tsPacketBuffer
,
payloadUnitStartIndicator
)
;
}
payloadReader
.
read
(
tsPacketBuffer
,
payloadUnitStartIndicator
);
}
return
0
;
if
(!
prepared
)
{
prepared
=
checkPrepared
();
}
tsPacketBuffer
.
reset
();
return
read
;
}
private
void
convert
(
Sample
in
,
SampleHolder
out
)
{
...
...
@@ -268,7 +269,7 @@ public final class TsExtractor {
*/
private
abstract
static
class
TsPayloadReader
{
public
abstract
void
read
(
Bit
s
Array
tsBuffer
,
boolean
payloadUnitStartIndicator
);
public
abstract
void
read
(
BitArray
tsBuffer
,
boolean
payloadUnitStartIndicator
);
}
...
...
@@ -278,7 +279,7 @@ public final class TsExtractor {
private
class
PatReader
extends
TsPayloadReader
{
@Override
public
void
read
(
Bit
s
Array
tsBuffer
,
boolean
payloadUnitStartIndicator
)
{
public
void
read
(
BitArray
tsBuffer
,
boolean
payloadUnitStartIndicator
)
{
// Skip pointer.
if
(
payloadUnitStartIndicator
)
{
int
pointerField
=
tsBuffer
.
readBits
(
8
);
...
...
@@ -311,7 +312,7 @@ public final class TsExtractor {
private
class
PmtReader
extends
TsPayloadReader
{
@Override
public
void
read
(
Bit
s
Array
tsBuffer
,
boolean
payloadUnitStartIndicator
)
{
public
void
read
(
BitArray
tsBuffer
,
boolean
payloadUnitStartIndicator
)
{
// Skip pointer.
if
(
payloadUnitStartIndicator
)
{
int
pointerField
=
tsBuffer
.
readBits
(
8
);
...
...
@@ -323,10 +324,10 @@ public final class TsExtractor {
int
sectionLength
=
tsBuffer
.
readBits
(
12
);
// Skip the rest of the PMT header.
tsBuffer
.
skipBits
(
60
);
// 16+2+5+1+8+8+3+13+4
int
programInfoLength
=
tsBuffer
.
readBits
(
12
);
// Read descriptors.
readDescriptors
(
tsBuffer
,
programInfoLength
);
int
programInfoLength
=
tsBuffer
.
readBits
(
12
);
// Skip the descriptors.
tsBuffer
.
skipBytes
(
programInfoLength
);
int
entriesSize
=
sectionLength
-
9
/* size of the rest of the fields before descriptors */
-
programInfoLength
-
4
/* CRC size */
;
...
...
@@ -335,9 +336,10 @@ public final class TsExtractor {
tsBuffer
.
skipBits
(
3
);
int
elementaryPid
=
tsBuffer
.
readBits
(
13
);
tsBuffer
.
skipBits
(
4
);
int
esInfoLength
=
tsBuffer
.
readBits
(
12
);
readDescriptors
(
tsBuffer
,
esInfoLength
);
int
esInfoLength
=
tsBuffer
.
readBits
(
12
);
// Skip the descriptors.
tsBuffer
.
skipBytes
(
esInfoLength
);
entriesSize
-=
esInfoLength
+
5
;
if
(
pesPayloadReaders
.
get
(
streamType
)
!=
null
)
{
...
...
@@ -366,19 +368,6 @@ public final class TsExtractor {
// Skip CRC_32.
}
private
void
readDescriptors
(
BitsArray
tsBuffer
,
int
descriptorsSize
)
{
while
(
descriptorsSize
>
0
)
{
// Skip tag.
tsBuffer
.
skipBits
(
8
);
int
descriptorsLength
=
tsBuffer
.
readBits
(
8
);
if
(
descriptorsLength
>
0
)
{
// Skip entire descriptor data.
tsBuffer
.
skipBytes
(
descriptorsLength
);
}
descriptorsSize
-=
descriptorsSize
+
2
;
}
}
}
/**
...
...
@@ -387,7 +376,7 @@ public final class TsExtractor {
private
class
PesReader
extends
TsPayloadReader
{
// Reusable buffer for incomplete PES data.
private
final
Bit
s
Array
pesBuffer
;
private
final
BitArray
pesBuffer
;
// Parses PES payload and extracts individual samples.
private
final
PesPayloadReader
pesPayloadReader
;
...
...
@@ -396,11 +385,11 @@ public final class TsExtractor {
public
PesReader
(
PesPayloadReader
pesPayloadReader
)
{
this
.
pesPayloadReader
=
pesPayloadReader
;
this
.
packetLength
=
-
1
;
pesBuffer
=
new
Bit
s
Array
();
pesBuffer
=
new
BitArray
();
}
@Override
public
void
read
(
Bit
s
Array
tsBuffer
,
boolean
payloadUnitStartIndicator
)
{
public
void
read
(
BitArray
tsBuffer
,
boolean
payloadUnitStartIndicator
)
{
if
(
payloadUnitStartIndicator
&&
!
pesBuffer
.
isEmpty
())
{
// We've encountered the start of the next packet, but haven't yet read the body. Read it.
// Note that this should only happen if the packet length was unspecified.
...
...
@@ -484,7 +473,7 @@ public final class TsExtractor {
*/
private
abstract
class
PesPayloadReader
{
public
final
Queue
<
Sample
>
sampleQueue
;
public
final
LinkedList
<
Sample
>
sampleQueue
;
private
MediaFormat
mediaFormat
;
private
boolean
foundFirstKeyframe
;
...
...
@@ -506,7 +495,7 @@ public final class TsExtractor {
this
.
mediaFormat
=
mediaFormat
;
}
public
abstract
void
read
(
Bit
s
Array
pesBuffer
,
int
pesPayloadSize
,
long
pesTimeUs
);
public
abstract
void
read
(
BitArray
pesBuffer
,
int
pesPayloadSize
,
long
pesTimeUs
);
public
void
clear
()
{
while
(!
sampleQueue
.
isEmpty
())
{
...
...
@@ -521,7 +510,7 @@ public final class TsExtractor {
* @param sampleSize The size of the sample data.
* @param sampleTimeUs The sample time stamp.
*/
protected
void
addSample
(
Bit
s
Array
buffer
,
int
sampleSize
,
long
sampleTimeUs
,
int
flags
)
{
protected
void
addSample
(
BitArray
buffer
,
int
sampleSize
,
long
sampleTimeUs
,
int
flags
)
{
Sample
sample
=
samplePool
.
get
();
addToSample
(
sample
,
buffer
,
sampleSize
);
sample
.
flags
=
flags
;
...
...
@@ -531,7 +520,7 @@ public final class TsExtractor {
@SuppressLint
(
"InlinedApi"
)
protected
void
addSample
(
Sample
sample
)
{
boolean
isKeyframe
=
(
sample
.
flags
&
Media
Codec
.
BUFFER_FLAG_SYNC_FRAME
)
!=
0
;
boolean
isKeyframe
=
(
sample
.
flags
&
Media
Extractor
.
SAMPLE_FLAG_SYNC
)
!=
0
;
if
(
isKeyframe
)
{
if
(!
foundFirstKeyframe
)
{
foundFirstKeyframe
=
true
;
...
...
@@ -549,7 +538,7 @@ public final class TsExtractor {
}
}
protected
void
addToSample
(
Sample
sample
,
Bit
s
Array
buffer
,
int
size
)
{
protected
void
addToSample
(
Sample
sample
,
BitArray
buffer
,
int
size
)
{
if
(
sample
.
data
.
length
-
sample
.
size
<
size
)
{
sample
.
expand
(
size
-
sample
.
data
.
length
+
sample
.
size
);
}
...
...
@@ -572,22 +561,24 @@ public final class TsExtractor {
*/
private
class
H264Reader
extends
PesPayloadReader
{
// IDR picture.
private
static
final
int
NAL_UNIT_TYPE_IDR
=
5
;
// Access unit delimiter.
private
static
final
int
NAL_UNIT_TYPE_AUD
=
9
;
private
static
final
int
NAL_UNIT_TYPE_SPS
=
7
;
// Used to store uncompleted sample data.
private
Sample
currentSample
;
public
H264Reader
()
{
// TODO: Parse the format from the stream.
setMediaFormat
(
MediaFormat
.
createVideoFormat
(
MimeTypes
.
VIDEO_H264
,
MediaFormat
.
NO_VALUE
,
1920
,
1080
,
null
));
@Override
public
void
clear
()
{
super
.
clear
();
if
(
currentSample
!=
null
)
{
samplePool
.
recycle
(
currentSample
);
currentSample
=
null
;
}
}
@Override
public
void
read
(
Bit
s
Array
pesBuffer
,
int
pesPayloadSize
,
long
pesTimeUs
)
{
public
void
read
(
BitArray
pesBuffer
,
int
pesPayloadSize
,
long
pesTimeUs
)
{
// Read leftover frame data from previous PES packet.
pesPayloadSize
-=
readOneH264Frame
(
pesBuffer
,
true
);
...
...
@@ -597,6 +588,9 @@ public final class TsExtractor {
// Single PES packet should contain only one new H.264 frame.
if
(
currentSample
!=
null
)
{
if
(!
hasMediaFormat
()
&&
(
currentSample
.
flags
&
MediaExtractor
.
SAMPLE_FLAG_SYNC
)
!=
0
)
{
parseMediaFormat
(
currentSample
);
}
addSample
(
currentSample
);
}
currentSample
=
samplePool
.
get
();
...
...
@@ -609,32 +603,120 @@ public final class TsExtractor {
}
@SuppressLint
(
"InlinedApi"
)
private
int
readOneH264Frame
(
Bit
s
Array
pesBuffer
,
boolean
remainderOnly
)
{
private
int
readOneH264Frame
(
BitArray
pesBuffer
,
boolean
remainderOnly
)
{
int
offset
=
remainderOnly
?
0
:
3
;
int
audStart
=
pesBuffer
.
findNextNalUnit
(
NAL_UNIT_TYPE_AUD
,
offset
);
int
idrStart
=
pesBuffer
.
findNextNalUnit
(
NAL_UNIT_TYPE_IDR
,
offset
);
if
(
audStart
>
0
)
{
if
(
currentSample
!=
null
)
{
addToSample
(
currentSample
,
pesBuffer
,
audStart
);
if
(
idrStart
<
audStart
)
{
currentSample
.
flags
=
MediaExtractor
.
SAMPLE_FLAG_SYNC
;
}
}
else
{
pesBuffer
.
skipBytes
(
audStart
);
if
(
currentSample
!=
null
)
{
int
idrStart
=
pesBuffer
.
findNextNalUnit
(
NAL_UNIT_TYPE_IDR
,
offset
);
if
(
idrStart
<
audStart
)
{
currentSample
.
flags
=
MediaExtractor
.
SAMPLE_FLAG_SYNC
;
}
return
audStart
;
addToSample
(
currentSample
,
pesBuffer
,
audStart
);
}
else
{
pesBuffer
.
skipBytes
(
audStart
);
}
return
0
;
return
audStart
;
}
@Override
public
void
clear
()
{
super
.
clear
();
if
(
currentSample
!=
null
)
{
samplePool
.
recycle
(
currentSample
);
currentSample
=
null
;
private
void
parseMediaFormat
(
Sample
sample
)
{
BitArray
bitArray
=
new
BitArray
(
sample
.
data
,
sample
.
size
);
// Locate the SPS unit.
int
spsOffset
=
bitArray
.
findNextNalUnit
(
NAL_UNIT_TYPE_SPS
,
0
);
if
(
spsOffset
==
bitArray
.
bytesLeft
())
{
return
;
}
int
nextNalOffset
=
bitArray
.
findNextNalUnit
(-
1
,
spsOffset
+
3
);
// Unescape the SPS unit.
byte
[]
unescapedSps
=
unescapeStream
(
bitArray
.
getData
(),
spsOffset
,
nextNalOffset
);
bitArray
.
reset
(
unescapedSps
,
unescapedSps
.
length
);
// Parse the SPS unit
// Skip the NAL header.
bitArray
.
skipBytes
(
4
);
// TODO: Handle different profiles properly.
bitArray
.
skipBytes
(
1
);
// Skip 6 constraint bits, 2 reserved bits and level_idc.
bitArray
.
skipBytes
(
2
);
// Skip seq_parameter_set_id.
bitArray
.
readExpGolombCodedInt
();
// Skip log2_max_frame_num_minus4
bitArray
.
readExpGolombCodedInt
();
long
picOrderCntType
=
bitArray
.
readExpGolombCodedInt
();
if
(
picOrderCntType
==
0
)
{
// Skip log2_max_pic_order_cnt_lsb_minus4
bitArray
.
readExpGolombCodedInt
();
}
else
if
(
picOrderCntType
==
1
)
{
// Skip delta_pic_order_always_zero_flag
bitArray
.
skipBits
(
1
);
// Skip offset_for_non_ref_pic (actually a signed value, but for skipping we can read it
// as though it were unsigned).
bitArray
.
readExpGolombCodedInt
();
// Skip offset_for_top_to_bottom_field (actually a signed value, but for skipping we can
// read it as though it were unsigned).
bitArray
.
readExpGolombCodedInt
();
long
numRefFramesInPicOrderCntCycle
=
bitArray
.
readExpGolombCodedInt
();
for
(
int
i
=
0
;
i
<
numRefFramesInPicOrderCntCycle
;
i
++)
{
// Skip offset_for_ref_frame[i]
bitArray
.
readExpGolombCodedInt
();
}
}
// Skip max_num_ref_frames
bitArray
.
readExpGolombCodedInt
();
// Skip gaps_in_frame_num_value_allowed_flag
bitArray
.
skipBits
(
1
);
int
picWidthInMbs
=
bitArray
.
readExpGolombCodedInt
()
+
1
;
int
picHeightInMapUnits
=
bitArray
.
readExpGolombCodedInt
()
+
1
;
boolean
frameMbsOnlyFlag
=
bitArray
.
readBit
();
int
frameHeightInMbs
=
(
2
-
(
frameMbsOnlyFlag
?
1
:
0
))
*
picHeightInMapUnits
;
// Set the format.
setMediaFormat
(
MediaFormat
.
createVideoFormat
(
MimeTypes
.
VIDEO_H264
,
MediaFormat
.
NO_VALUE
,
picWidthInMbs
*
16
,
frameHeightInMbs
*
16
,
null
));
}
/**
* Replaces occurrences of [0, 0, 3] with [0, 0].
* <p>
* See ISO/IEC 14496-10:2005(E) page 36 for more information.
*/
private
byte
[]
unescapeStream
(
byte
[]
data
,
int
offset
,
int
limit
)
{
int
position
=
offset
;
List
<
Integer
>
escapePositions
=
new
ArrayList
<
Integer
>();
while
(
position
<
limit
)
{
position
=
findNextUnescapeIndex
(
data
,
position
,
limit
);
if
(
position
<
limit
)
{
escapePositions
.
add
(
position
);
position
+=
3
;
}
}
int
escapeCount
=
escapePositions
.
size
();
int
escapedPosition
=
offset
;
// The position being read from.
int
unescapedPosition
=
0
;
// The position being written to.
byte
[]
unescapedData
=
new
byte
[
limit
-
offset
-
escapeCount
];
for
(
int
i
=
0
;
i
<
escapeCount
;
i
++)
{
int
nextEscapePosition
=
escapePositions
.
get
(
i
);
int
copyLength
=
nextEscapePosition
-
escapedPosition
;
System
.
arraycopy
(
data
,
escapedPosition
,
unescapedData
,
unescapedPosition
,
copyLength
);
escapedPosition
+=
copyLength
+
3
;
unescapedPosition
+=
copyLength
+
2
;
}
int
remainingLength
=
unescapedData
.
length
-
unescapedPosition
;
System
.
arraycopy
(
data
,
escapedPosition
,
unescapedData
,
unescapedPosition
,
remainingLength
);
return
unescapedData
;
}
private
int
findNextUnescapeIndex
(
byte
[]
bytes
,
int
offset
,
int
limit
)
{
for
(
int
i
=
offset
;
i
<
limit
-
2
;
i
++)
{
if
(
bytes
[
i
]
==
0x00
&&
bytes
[
i
+
1
]
==
0x00
&&
bytes
[
i
+
2
]
==
0x03
)
{
return
i
;
}
}
return
limit
;
}
}
/**
...
...
@@ -642,15 +724,15 @@ public final class TsExtractor {
*/
private
class
AdtsReader
extends
PesPayloadReader
{
private
final
Bit
s
Array
adtsBuffer
;
private
final
BitArray
adtsBuffer
;
private
long
timeUs
;
public
AdtsReader
()
{
adtsBuffer
=
new
Bit
s
Array
();
adtsBuffer
=
new
BitArray
();
}
@Override
public
void
read
(
Bit
s
Array
pesBuffer
,
int
pesPayloadSize
,
long
pesTimeUs
)
{
public
void
read
(
BitArray
pesBuffer
,
int
pesPayloadSize
,
long
pesTimeUs
)
{
boolean
needToProcessLeftOvers
=
!
adtsBuffer
.
isEmpty
();
adtsBuffer
.
append
(
pesBuffer
,
pesPayloadSize
);
// If there are leftovers from previous PES packet, process it with last calculated timeUs.
...
...
@@ -751,7 +833,7 @@ public final class TsExtractor {
@SuppressLint
(
"InlinedApi"
)
@Override
public
void
read
(
Bit
s
Array
pesBuffer
,
int
pesPayloadSize
,
long
pesTimeUs
)
{
public
void
read
(
BitArray
pesBuffer
,
int
pesPayloadSize
,
long
pesTimeUs
)
{
addSample
(
pesBuffer
,
pesPayloadSize
,
pesTimeUs
,
MediaExtractor
.
SAMPLE_FLAG_SYNC
);
}
...
...
@@ -764,31 +846,33 @@ public final class TsExtractor {
private
static
final
int
DEFAULT_BUFFER_SEGMENT_SIZE
=
64
*
1024
;
private
final
ArrayList
<
Sample
>
samples
;
private
Sample
firstInPool
;
public
SamplePool
()
{
samples
=
new
ArrayList
<
Sample
>();
}
/* package */
Sample
get
()
{
if
(
samples
.
isEmpty
())
{
/* package */
synchronized
Sample
get
()
{
if
(
firstInPool
==
null
)
{
return
new
Sample
(
DEFAULT_BUFFER_SEGMENT_SIZE
);
}
return
samples
.
remove
(
samples
.
size
()
-
1
);
Sample
sample
=
firstInPool
;
firstInPool
=
sample
.
nextInPool
;
sample
.
nextInPool
=
null
;
return
sample
;
}
/* package */
void
recycle
(
Sample
sample
)
{
/* package */
synchronized
void
recycle
(
Sample
sample
)
{
sample
.
reset
();
samples
.
add
(
sample
);
sample
.
nextInPool
=
firstInPool
;
firstInPool
=
sample
;
}
}
/**
*
Simplified version of SampleHolder for internal
buffering.
*
An internal variant of {@link SampleHolder} for internal pooling and
buffering.
*/
private
static
class
Sample
{
public
Sample
nextInPool
;
public
byte
[]
data
;
public
int
flags
;
public
int
size
;
...
...
library/src/main/java/com/google/android/exoplayer/hls/
HlsChunkOperationHolder
.java
→
library/src/main/java/com/google/android/exoplayer/hls/
Variant
.java
View file @
fd519016
...
...
@@ -15,23 +15,42 @@
*/
package
com
.
google
.
android
.
exoplayer
.
hls
;
import
java.util.Comparator
;
/**
* Holds a hls chunk operation, which consists of a {@link HlsChunk} to load together with the
* number of {@link TsChunk}s that should be retained on the queue.
* <p>
* TODO: Figure out whether this should merge with the chunk package, or whether the hls
* implementation is going to naturally diverge.
* Variant stream reference.
*/
public
final
class
HlsChunkOperationHolder
{
public
final
class
Variant
{
/**
* The number of {@link TsChunk}s to retain in a queue.
* Sorts {@link Variant} objects in order of decreasing bandwidth.
* <p>
* When two {@link Variant}s have the same bandwidth, the one with the lowest index comes first.
*/
public
int
queueSize
;
public
static
final
class
DecreasingBandwidthComparator
implements
Comparator
<
Variant
>
{
/**
* The chunk.
*/
public
HlsChunk
chunk
;
@Override
public
int
compare
(
Variant
a
,
Variant
b
)
{
int
bandwidthDifference
=
b
.
bandwidth
-
a
.
bandwidth
;
return
bandwidthDifference
!=
0
?
bandwidthDifference
:
a
.
index
-
b
.
index
;
}
}
public
final
int
index
;
public
final
int
bandwidth
;
public
final
String
url
;
public
final
String
[]
codecs
;
public
final
int
width
;
public
final
int
height
;
public
Variant
(
int
index
,
String
url
,
int
bandwidth
,
String
[]
codecs
,
int
width
,
int
height
)
{
this
.
index
=
index
;
this
.
bandwidth
=
bandwidth
;
this
.
url
=
url
;
this
.
codecs
=
codecs
;
this
.
width
=
width
;
this
.
height
=
height
;
}
}
library/src/main/java/com/google/android/exoplayer/metadata/Id3Parser.java
View file @
fd519016
...
...
@@ -16,7 +16,7 @@
package
com
.
google
.
android
.
exoplayer
.
metadata
;
import
com.google.android.exoplayer.ParserException
;
import
com.google.android.exoplayer.
parser.ts.Bits
Array
;
import
com.google.android.exoplayer.
util.Bit
Array
;
import
com.google.android.exoplayer.util.MimeTypes
;
import
java.io.UnsupportedEncodingException
;
...
...
@@ -37,7 +37,7 @@ public class Id3Parser implements MetadataParser {
@Override
public
Map
<
String
,
Object
>
parse
(
byte
[]
data
,
int
size
)
throws
UnsupportedEncodingException
,
ParserException
{
Bit
sArray
id3Buffer
=
new
Bits
Array
(
data
,
size
);
Bit
Array
id3Buffer
=
new
Bit
Array
(
data
,
size
);
int
id3Size
=
parseId3Header
(
id3Buffer
);
Map
<
String
,
Object
>
metadata
=
new
HashMap
<
String
,
Object
>();
...
...
@@ -102,11 +102,11 @@ public class Id3Parser implements MetadataParser {
/**
* Parses ID3 header.
* @param id3Buffer A {@link Bit
s
Array} with raw ID3 data.
* @param id3Buffer A {@link BitArray} with raw ID3 data.
* @return The size of data that contains ID3 frames without header and footer.
* @throws ParserException If ID3 file identifier != "ID3".
*/
private
static
int
parseId3Header
(
Bit
s
Array
id3Buffer
)
throws
ParserException
{
private
static
int
parseId3Header
(
BitArray
id3Buffer
)
throws
ParserException
{
int
id1
=
id3Buffer
.
readUnsignedByte
();
int
id2
=
id3Buffer
.
readUnsignedByte
();
int
id3
=
id3Buffer
.
readUnsignedByte
();
...
...
library/src/main/java/com/google/android/exoplayer/metadata/MetadataParser.java
View file @
fd519016
...
...
@@ -19,7 +19,7 @@ import java.io.IOException;
import
java.util.Map
;
/**
* Parses
{@link Metadata}
s from binary data.
* Parses
metadata object
s from binary data.
*/
public
interface
MetadataParser
{
...
...
library/src/main/java/com/google/android/exoplayer/
parser/ts/Bits
Array.java
→
library/src/main/java/com/google/android/exoplayer/
util/Bit
Array.java
View file @
fd519016
...
...
@@ -13,15 +13,16 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package
com
.
google
.
android
.
exoplayer
.
parser
.
ts
;
package
com
.
google
.
android
.
exoplayer
.
util
;
import
com.google.android.exoplayer.upstream.NonBlockingInputStream
;
import
com.google.android.exoplayer.util.Assertions
;
import
com.google.android.exoplayer.upstream.DataSource
;
import
java.io.IOException
;
/**
* Wraps a byte array, providing methods that allow it to be read as a bitstream.
*/
public
final
class
Bit
s
Array
{
public
final
class
BitArray
{
private
byte
[]
data
;
...
...
@@ -33,16 +34,16 @@ public final class BitsArray {
private
int
byteOffset
;
private
int
bitOffset
;
public
Bit
s
Array
()
{
public
BitArray
()
{
}
public
Bit
s
Array
(
byte
[]
data
,
int
limit
)
{
public
BitArray
(
byte
[]
data
,
int
limit
)
{
this
.
data
=
data
;
this
.
limit
=
limit
;
}
/**
*
Resets the state
.
*
Clears all data, setting the offset and limit to zero
.
*/
public
void
reset
()
{
byteOffset
=
0
;
...
...
@@ -51,6 +52,28 @@ public final class BitsArray {
}
/**
* Resets to wrap the specified data, setting the offset to zero.
*
* @param data The data to wrap.
* @param limit The limit to set.
*/
public
void
reset
(
byte
[]
data
,
int
limit
)
{
this
.
data
=
data
;
this
.
limit
=
limit
;
byteOffset
=
0
;
bitOffset
=
0
;
}
/**
* Gets the backing byte array.
*
* @return The backing byte array.
*/
public
byte
[]
getData
()
{
return
data
;
}
/**
* Gets the current byte offset.
*
* @return The current byte offset.
...
...
@@ -69,16 +92,16 @@ public final class BitsArray {
}
/**
* Appends data from a {@link
NonBlockingInputStream
}.
* Appends data from a {@link
DataSource
}.
*
* @param
inputStream The {@link NonBlockingInputStream} whose data should be appende
d.
* @param
dataSource The {@link DataSource} from which to rea
d.
* @param length The maximum number of bytes to read and append.
* @return The number of bytes that were read and appended
. May be 0 if no data was available
*
from the stream. -1 is returned if the end of the stream has been reached
.
* @return The number of bytes that were read and appended
, or -1 if no more data is available.
*
@throws IOException If an error occurs reading from the source
.
*/
public
int
append
(
NonBlockingInputStream
inputStream
,
int
length
)
{
public
int
append
(
DataSource
dataSource
,
int
length
)
throws
IOException
{
expand
(
length
);
int
bytesRead
=
inputStream
.
read
(
data
,
limit
,
length
);
int
bytesRead
=
dataSource
.
read
(
data
,
limit
,
length
);
if
(
bytesRead
==
-
1
)
{
return
-
1
;
}
...
...
@@ -87,12 +110,12 @@ public final class BitsArray {
}
/**
* Appends data from another {@link Bit
s
Array}.
* Appends data from another {@link BitArray}.
*
* @param bitsArray The {@link Bit
s
Array} whose data should be appended.
* @param bitsArray The {@link BitArray} whose data should be appended.
* @param length The number of bytes to read and append.
*/
public
void
append
(
Bit
s
Array
bitsArray
,
int
length
)
{
public
void
append
(
BitArray
bitsArray
,
int
length
)
{
expand
(
length
);
bitsArray
.
readBytes
(
data
,
limit
,
length
);
limit
+=
length
;
...
...
@@ -257,6 +280,19 @@ public final class BitsArray {
}
/**
* Reads an Exp-Golomb-coded format integer.
*
* @return The value of the parsed Exp-Golomb-coded integer.
*/
public
int
readExpGolombCodedInt
()
{
int
leadingZeros
=
0
;
while
(!
readBit
())
{
leadingZeros
++;
}
return
(
1
<<
leadingZeros
)
-
1
+
(
leadingZeros
>
0
?
readBits
(
leadingZeros
)
:
0
);
}
/**
* Reads a Synchsafe integer.
* Synchsafe integers are integers that keep the highest bit of every byte zeroed.
* A 32 bit synchsafe integer can store 28 bits of information.
...
...
@@ -293,7 +329,7 @@ public final class BitsArray {
/**
* Finds the next NAL unit.
*
* @param nalUnitType The type of the NAL unit to search for.
* @param nalUnitType The type of the NAL unit to search for
, or -1 for any NAL unit
.
* @param offset The additional offset in the data to start the search from.
* @return The offset from the current position to the start of the NAL unit. If a NAL unit is
* not found, then the offset to the end of the data is returned.
...
...
@@ -302,7 +338,7 @@ public final class BitsArray {
for
(
int
i
=
byteOffset
+
offset
;
i
<
limit
-
3
;
i
++)
{
// Check for NAL unit start code prefix == 0x000001.
if
((
data
[
i
]
==
0
&&
data
[
i
+
1
]
==
0
&&
data
[
i
+
2
]
==
1
)
&&
(
nalUnitType
==
(
data
[
i
+
3
]
&
0x1F
)))
{
&&
(
nalUnitType
==
-
1
||
(
nalUnitType
==
(
data
[
i
+
3
]
&
0x1F
)
)))
{
return
i
-
byteOffset
;
}
}
...
...
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