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
3ada4e17
authored
Jun 14, 2017
by
ojw28
Committed by
GitHub
Jun 14, 2017
Browse files
Options
_('Browse Files')
Download
Plain Diff
Merge pull request #2952 from google/dev-v2-r2.4.2
r2.4.2
parents
9f814850
4e578a1b
Hide whitespace changes
Inline
Side-by-side
Showing
27 changed files
with
368 additions
and
236 deletions
RELEASENOTES.md
build.gradle
demo/src/main/AndroidManifest.xml
demo/src/main/java/com/google/android/exoplayer2/demo/PlayerActivity.java
library/core/src/androidTest/java/com/google/android/exoplayer2/util/UtilTest.java
library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImpl.java
library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerLibraryInfo.java
library/core/src/main/java/com/google/android/exoplayer2/extractor/mkv/MatroskaExtractor.java
library/core/src/main/java/com/google/android/exoplayer2/extractor/mp3/ConstantBitrateSeeker.java
library/core/src/main/java/com/google/android/exoplayer2/extractor/mp3/Mp3Extractor.java
library/core/src/main/java/com/google/android/exoplayer2/extractor/ts/TsExtractor.java
library/core/src/main/java/com/google/android/exoplayer2/mediacodec/MediaCodecInfo.java
library/core/src/main/java/com/google/android/exoplayer2/mediacodec/MediaCodecRenderer.java
library/core/src/main/java/com/google/android/exoplayer2/mediacodec/MediaCodecUtil.java
library/core/src/main/java/com/google/android/exoplayer2/trackselection/AdaptiveTrackSelection.java
library/core/src/main/java/com/google/android/exoplayer2/trackselection/DefaultTrackSelector.java
library/core/src/main/java/com/google/android/exoplayer2/util/MimeTypes.java
library/core/src/main/java/com/google/android/exoplayer2/util/Util.java
library/core/src/main/java/com/google/android/exoplayer2/video/DummySurface.java
library/core/src/main/java/com/google/android/exoplayer2/video/MediaCodecVideoRenderer.java
library/dash/src/main/java/com/google/android/exoplayer2/source/dash/DashMediaSource.java
library/hls/src/androidTest/java/com/google/android/exoplayer2/source/hls/playlist/HlsMasterPlaylistParserTest.java
library/hls/src/main/java/com/google/android/exoplayer2/source/hls/playlist/HlsPlaylistParser.java
library/smoothstreaming/src/main/java/com/google/android/exoplayer2/source/smoothstreaming/SsMediaSource.java
library/ui/src/main/java/com/google/android/exoplayer2/ui/DefaultTimeBar.java
library/ui/src/main/java/com/google/android/exoplayer2/ui/PlaybackControlView.java
library/ui/src/main/java/com/google/android/exoplayer2/ui/TimeBar.java
RELEASENOTES.md
View file @
3ada4e17
# Release notes #
### r2.4.2 ###
*
Stability: Work around Nexus 10 reboot when playing certain content
(
[
2806
](
https://github.com/google/ExoPlayer/issues/2806
)
).
*
MP3: Correctly treat MP3s with INFO headers as constant bitrate
(
[
2895
](
https://github.com/google/ExoPlayer/issues/2895
)
).
*
HLS: Use average rather than peak bandwidth when available
(
[
#2863
](
https://github.com/google/ExoPlayer/issues/2863
)
).
*
SmoothStreaming: Fix timeline for live streams
(
[
#2760
](
https://github.com/google/ExoPlayer/issues/2760
)
).
*
UI: Fix DefaultTimeBar invalidation
(
[
#2871
](
https://github.com/google/ExoPlayer/issues/2871
)
).
*
Misc bugfixes.
### r2.4.1 ###
*
Stability: Avoid OutOfMemoryError in extractors when parsing malformed media
...
...
build.gradle
View file @
3ada4e17
...
...
@@ -48,7 +48,7 @@ allprojects {
releaseRepoName
=
getBintrayRepo
()
releaseUserOrg
=
'google'
releaseGroupId
=
'com.google.android.exoplayer'
releaseVersion
=
'r2.4.
1
'
releaseVersion
=
'r2.4.
2
'
releaseWebsite
=
'https://github.com/google/ExoPlayer'
}
if
(
it
.
hasProperty
(
'externalBuildDir'
))
{
...
...
demo/src/main/AndroidManifest.xml
View file @
3ada4e17
...
...
@@ -16,8 +16,8 @@
<manifest
xmlns:android=
"http://schemas.android.com/apk/res/android"
package=
"com.google.android.exoplayer2.demo"
android:versionCode=
"240
1
"
android:versionName=
"2.4.
1
"
>
android:versionCode=
"240
2
"
android:versionName=
"2.4.
2
"
>
<uses-permission
android:name=
"android.permission.INTERNET"
/>
<uses-permission
android:name=
"android.permission.READ_EXTERNAL_STORAGE"
/>
...
...
demo/src/main/java/com/google/android/exoplayer2/demo/PlayerActivity.java
View file @
3ada4e17
...
...
@@ -234,6 +234,13 @@ public class PlayerActivity extends Activity implements OnClickListener, ExoPlay
Intent
intent
=
getIntent
();
boolean
needNewPlayer
=
player
==
null
;
if
(
needNewPlayer
)
{
TrackSelection
.
Factory
adaptiveTrackSelectionFactory
=
new
AdaptiveTrackSelection
.
Factory
(
BANDWIDTH_METER
);
trackSelector
=
new
DefaultTrackSelector
(
adaptiveTrackSelectionFactory
);
trackSelectionHelper
=
new
TrackSelectionHelper
(
trackSelector
,
adaptiveTrackSelectionFactory
);
lastSeenTrackGroupArray
=
null
;
eventLogger
=
new
EventLogger
(
trackSelector
);
UUID
drmSchemeUuid
=
intent
.
hasExtra
(
DRM_SCHEME_UUID_EXTRA
)
?
UUID
.
fromString
(
intent
.
getStringExtra
(
DRM_SCHEME_UUID_EXTRA
))
:
null
;
DrmSessionManager
<
FrameworkMediaCrypto
>
drmSessionManager
=
null
;
...
...
@@ -261,16 +268,8 @@ public class PlayerActivity extends Activity implements OnClickListener, ExoPlay
DefaultRenderersFactory
renderersFactory
=
new
DefaultRenderersFactory
(
this
,
drmSessionManager
,
extensionRendererMode
);
TrackSelection
.
Factory
adaptiveTrackSelectionFactory
=
new
AdaptiveTrackSelection
.
Factory
(
BANDWIDTH_METER
);
trackSelector
=
new
DefaultTrackSelector
(
adaptiveTrackSelectionFactory
);
trackSelectionHelper
=
new
TrackSelectionHelper
(
trackSelector
,
adaptiveTrackSelectionFactory
);
lastSeenTrackGroupArray
=
null
;
player
=
ExoPlayerFactory
.
newSimpleInstance
(
renderersFactory
,
trackSelector
);
player
.
addListener
(
this
);
eventLogger
=
new
EventLogger
(
trackSelector
);
player
.
addListener
(
eventLogger
);
player
.
setAudioDebugListener
(
eventLogger
);
player
.
setVideoDebugListener
(
eventLogger
);
...
...
library/core/src/androidTest/java/com/google/android/exoplayer2/util/UtilTest.java
View file @
3ada4e17
...
...
@@ -146,6 +146,7 @@ public class UtilTest extends TestCase {
assertEquals
(
1411161535000L
,
Util
.
parseXsDateTime
(
"2014-09-19T13:18:55-08:00"
));
assertEquals
(
1411161535000L
,
Util
.
parseXsDateTime
(
"2014-09-19T13:18:55-0800"
));
assertEquals
(
1411161535000L
,
Util
.
parseXsDateTime
(
"2014-09-19T13:18:55.000-0800"
));
assertEquals
(
1411161535000L
,
Util
.
parseXsDateTime
(
"2014-09-19T13:18:55.000-800"
));
}
public
void
testUnescapeInvalidFileName
()
{
...
...
library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImpl.java
View file @
3ada4e17
...
...
@@ -305,10 +305,10 @@ import java.util.concurrent.CopyOnWriteArraySet;
if
(
timeline
.
isEmpty
())
{
return
0
;
}
long
bufferedP
osition
=
getBufferedPosition
();
long
p
osition
=
getBufferedPosition
();
long
duration
=
getDuration
();
return
(
bufferedPosition
==
C
.
TIME_UNSET
||
duration
==
C
.
TIME_UNSET
)
?
0
:
(
int
)
(
duration
==
0
?
100
:
(
bufferedPosition
*
100
)
/
duration
);
return
position
==
C
.
TIME_UNSET
||
duration
==
C
.
TIME_UNSET
?
0
:
(
duration
==
0
?
100
:
Util
.
constrainValue
((
int
)
((
position
*
100
)
/
duration
),
0
,
100
)
);
}
@Override
...
...
library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerLibraryInfo.java
View file @
3ada4e17
...
...
@@ -24,13 +24,13 @@ public interface ExoPlayerLibraryInfo {
* The version of the library expressed as a string, for example "1.2.3".
*/
// Intentionally hardcoded. Do not derive from other constants (e.g. VERSION_INT) or vice versa.
String
VERSION
=
"2.4.
1
"
;
String
VERSION
=
"2.4.
2
"
;
/**
* The version of the library expressed as {@code "ExoPlayerLib/" + VERSION}.
*/
// Intentionally hardcoded. Do not derive from other constants (e.g. VERSION) or vice versa.
String
VERSION_SLASHY
=
"ExoPlayerLib/2.4.
1
"
;
String
VERSION_SLASHY
=
"ExoPlayerLib/2.4.
2
"
;
/**
* The version of the library expressed as an integer, for example 1002003.
...
...
@@ -40,7 +40,7 @@ public interface ExoPlayerLibraryInfo {
* integer version 123045006 (123-045-006).
*/
// Intentionally hardcoded. Do not derive from other constants (e.g. VERSION) or vice versa.
int
VERSION_INT
=
200400
1
;
int
VERSION_INT
=
200400
2
;
/**
* Whether the library was compiled with {@link com.google.android.exoplayer2.util.Assertions}
...
...
library/core/src/main/java/com/google/android/exoplayer2/extractor/mkv/MatroskaExtractor.java
View file @
3ada4e17
...
...
@@ -16,6 +16,7 @@
package
com
.
google
.
android
.
exoplayer2
.
extractor
.
mkv
;
import
android.support.annotation.IntDef
;
import
android.util.Log
;
import
android.util.SparseArray
;
import
com.google.android.exoplayer2.C
;
import
com.google.android.exoplayer2.Format
;
...
...
@@ -84,6 +85,8 @@ public final class MatroskaExtractor implements Extractor {
*/
public
static
final
int
FLAG_DISABLE_SEEK_FOR_CUES
=
1
;
private
static
final
String
TAG
=
"MatroskaExtractor"
;
private
static
final
int
UNSET_ENTRY_ID
=
-
1
;
private
static
final
int
BLOCK_STATE_START
=
0
;
...
...
@@ -1558,7 +1561,12 @@ public final class MatroskaExtractor implements Extractor {
break
;
case
CODEC_ID_FOURCC:
initializationData
=
parseFourCcVc1Private
(
new
ParsableByteArray
(
codecPrivate
));
mimeType
=
initializationData
==
null
?
MimeTypes
.
VIDEO_UNKNOWN
:
MimeTypes
.
VIDEO_VC1
;
if
(
initializationData
!=
null
)
{
mimeType
=
MimeTypes
.
VIDEO_VC1
;
}
else
{
Log
.
w
(
TAG
,
"Unsupported FourCC. Setting mimeType to "
+
MimeTypes
.
VIDEO_UNKNOWN
);
mimeType
=
MimeTypes
.
VIDEO_UNKNOWN
;
}
break
;
case
CODEC_ID_THEORA:
// TODO: This can be set to the real mimeType if/when we work out what initializationData
...
...
@@ -1614,19 +1622,27 @@ public final class MatroskaExtractor implements Extractor {
break
;
case
CODEC_ID_ACM:
mimeType
=
MimeTypes
.
AUDIO_RAW
;
if
(!
parseMsAcmCodecPrivate
(
new
ParsableByteArray
(
codecPrivate
)))
{
throw
new
ParserException
(
"Non-PCM MS/ACM is unsupported"
);
}
pcmEncoding
=
Util
.
getPcmEncoding
(
audioBitDepth
);
if
(
pcmEncoding
==
C
.
ENCODING_INVALID
)
{
throw
new
ParserException
(
"Unsupported PCM bit depth: "
+
audioBitDepth
);
if
(
parseMsAcmCodecPrivate
(
new
ParsableByteArray
(
codecPrivate
)))
{
pcmEncoding
=
Util
.
getPcmEncoding
(
audioBitDepth
);
if
(
pcmEncoding
==
C
.
ENCODING_INVALID
)
{
pcmEncoding
=
Format
.
NO_VALUE
;
mimeType
=
MimeTypes
.
AUDIO_UNKNOWN
;
Log
.
w
(
TAG
,
"Unsupported PCM bit depth: "
+
audioBitDepth
+
". Setting mimeType to "
+
mimeType
);
}
}
else
{
mimeType
=
MimeTypes
.
AUDIO_UNKNOWN
;
Log
.
w
(
TAG
,
"Non-PCM MS/ACM is unsupported. Setting mimeType to "
+
mimeType
);
}
break
;
case
CODEC_ID_PCM_INT_LIT:
mimeType
=
MimeTypes
.
AUDIO_RAW
;
pcmEncoding
=
Util
.
getPcmEncoding
(
audioBitDepth
);
if
(
pcmEncoding
==
C
.
ENCODING_INVALID
)
{
throw
new
ParserException
(
"Unsupported PCM bit depth: "
+
audioBitDepth
);
pcmEncoding
=
Format
.
NO_VALUE
;
mimeType
=
MimeTypes
.
AUDIO_UNKNOWN
;
Log
.
w
(
TAG
,
"Unsupported PCM bit depth: "
+
audioBitDepth
+
". Setting mimeType to "
+
mimeType
);
}
break
;
case
CODEC_ID_SUBRIP:
...
...
library/core/src/main/java/com/google/android/exoplayer2/extractor/mp3/ConstantBitrateSeeker.java
View file @
3ada4e17
...
...
@@ -16,6 +16,7 @@
package
com
.
google
.
android
.
exoplayer2
.
extractor
.
mp3
;
import
com.google.android.exoplayer2.C
;
import
com.google.android.exoplayer2.util.Util
;
/**
* MP3 seeker that doesn't rely on metadata and seeks assuming the source has a constant bitrate.
...
...
@@ -41,8 +42,11 @@ import com.google.android.exoplayer2.C;
@Override
public
long
getPosition
(
long
timeUs
)
{
return
durationUs
==
C
.
TIME_UNSET
?
0
:
firstFramePosition
+
(
timeUs
*
bitrate
)
/
(
C
.
MICROS_PER_SECOND
*
BITS_PER_BYTE
);
if
(
durationUs
==
C
.
TIME_UNSET
)
{
return
0
;
}
timeUs
=
Util
.
constrainValue
(
timeUs
,
0
,
durationUs
);
return
firstFramePosition
+
(
timeUs
*
bitrate
)
/
(
C
.
MICROS_PER_SECOND
*
BITS_PER_BYTE
);
}
@Override
...
...
library/core/src/main/java/com/google/android/exoplayer2/extractor/mp3/Mp3Extractor.java
View file @
3ada4e17
...
...
@@ -78,7 +78,7 @@ public final class Mp3Extractor implements Extractor {
/**
* The maximum number of bytes to peek when sniffing, excluding the ID3 header, before giving up.
*/
private
static
final
int
MAX_SNIFF_BYTES
=
MpegAudioHeader
.
MAX_FRAME_SIZE_BYTES
;
private
static
final
int
MAX_SNIFF_BYTES
=
16
*
1024
;
/**
* Maximum length of data read into {@link #scratch}.
*/
...
...
@@ -87,10 +87,12 @@ public final class Mp3Extractor implements Extractor {
/**
* Mask that includes the audio header values that must match between frames.
*/
private
static
final
int
HEADER_MASK
=
0xFFFE0C00
;
private
static
final
int
XING_HEADER
=
Util
.
getIntegerCodeForString
(
"Xing"
);
private
static
final
int
INFO_HEADER
=
Util
.
getIntegerCodeForString
(
"Info"
);
private
static
final
int
VBRI_HEADER
=
Util
.
getIntegerCodeForString
(
"VBRI"
);
private
static
final
int
MPEG_AUDIO_HEADER_MASK
=
0xFFFE0C00
;
private
static
final
int
SEEK_HEADER_XING
=
Util
.
getIntegerCodeForString
(
"Xing"
);
private
static
final
int
SEEK_HEADER_INFO
=
Util
.
getIntegerCodeForString
(
"Info"
);
private
static
final
int
SEEK_HEADER_VBRI
=
Util
.
getIntegerCodeForString
(
"VBRI"
);
private
static
final
int
SEEK_HEADER_UNSET
=
0
;
@Flags
private
final
int
flags
;
private
final
long
forcedFirstSampleTimestampUs
;
...
...
@@ -178,7 +180,11 @@ public final class Mp3Extractor implements Extractor {
}
}
if
(
seeker
==
null
)
{
seeker
=
setupSeeker
(
input
);
seeker
=
maybeReadSeekFrame
(
input
);
if
(
seeker
==
null
||
(!
seeker
.
isSeekable
()
&&
(
flags
&
FLAG_ENABLE_CONSTANT_BITRATE_SEEKING
)
!=
0
))
{
seeker
=
getConstantBitrateSeeker
(
input
);
}
extractorOutput
.
seekMap
(
seeker
);
trackOutput
.
format
(
Format
.
createAudioSampleFormat
(
null
,
synchronizedHeader
.
mimeType
,
null
,
Format
.
NO_VALUE
,
MpegAudioHeader
.
MAX_FRAME_SIZE_BYTES
,
synchronizedHeader
.
channels
,
...
...
@@ -197,7 +203,7 @@ public final class Mp3Extractor implements Extractor {
}
scratch
.
setPosition
(
0
);
int
sampleHeaderData
=
scratch
.
readInt
();
if
(
(
sampleHeaderData
&
HEADER_MASK
)
!=
(
synchronizedHeaderData
&
HEADER_MASK
)
if
(
!
headersMatch
(
sampleHeaderData
,
synchronizedHeaderData
)
||
MpegAudioHeader
.
getFrameSize
(
sampleHeaderData
)
==
C
.
LENGTH_UNSET
)
{
// We have lost synchronization, so attempt to resynchronize starting at the next byte.
extractorInput
.
skipFully
(
1
);
...
...
@@ -254,7 +260,7 @@ public final class Mp3Extractor implements Extractor {
int
headerData
=
scratch
.
readInt
();
int
frameSize
;
if
((
candidateSynchronizedHeaderData
!=
0
&&
(
headerData
&
HEADER_MASK
)
!=
(
candidateSynchronizedHeaderData
&
HEADER_MASK
))
&&
!
headersMatch
(
headerData
,
candidateSynchronizedHeaderData
))
||
(
frameSize
=
MpegAudioHeader
.
getFrameSize
(
headerData
))
==
C
.
LENGTH_UNSET
)
{
// The header doesn't match the candidate header or is invalid. Try the next byte offset.
if
(
searchedBytes
++
==
searchLimitBytes
)
{
...
...
@@ -337,37 +343,27 @@ public final class Mp3Extractor implements Extractor {
}
/**
*
Returns a {@link Seeker} to seek using metadata read from {@code input}, which should provide
*
data from the start of the first frame in the stream. On returning, the input's position will
*
be set to
the start of the first frame of audio.
*
Consumes the next frame from the {@code input} if it contains VBRI or Xing seeking metadata,
*
returning a {@link Seeker} if the metadata was present and valid, or {@code null} otherwise.
*
After this method returns, the input position is
the start of the first frame of audio.
*
* @param input The {@link ExtractorInput} from which to read.
* @return A {@link Seeker} if seeking metadata was present and valid, or {@code null} otherwise.
* @throws IOException Thrown if there was an error reading from the stream. Not expected if the
* next two frames were already peeked during synchronization.
* @throws InterruptedException Thrown if reading from the stream was interrupted. Not expected if
* the next two frames were already peeked during synchronization.
* @return a {@link Seeker}.
*/
private
Seeker
setupSeeker
(
ExtractorInput
input
)
throws
IOException
,
InterruptedException
{
// Read the first frame which may contain a Xing or VBRI header with seeking metadata.
private
Seeker
maybeReadSeekFrame
(
ExtractorInput
input
)
throws
IOException
,
InterruptedException
{
ParsableByteArray
frame
=
new
ParsableByteArray
(
synchronizedHeader
.
frameSize
);
input
.
peekFully
(
frame
.
data
,
0
,
synchronizedHeader
.
frameSize
);
long
position
=
input
.
getPosition
();
long
length
=
input
.
getLength
();
int
headerData
=
0
;
Seeker
seeker
=
null
;
// Check if there is a Xing header.
int
xingBase
=
(
synchronizedHeader
.
version
&
1
)
!=
0
?
(
synchronizedHeader
.
channels
!=
1
?
36
:
21
)
// MPEG 1
:
(
synchronizedHeader
.
channels
!=
1
?
21
:
13
);
// MPEG 2 or 2.5
if
(
frame
.
limit
()
>=
xingBase
+
4
)
{
frame
.
setPosition
(
xingBase
);
headerData
=
frame
.
readInt
();
}
if
(
headerData
==
XING_HEADER
||
headerData
==
INFO_HEADER
)
{
seeker
=
XingSeeker
.
create
(
synchronizedHeader
,
frame
,
position
,
length
);
int
seekHeader
=
getSeekFrameHeader
(
frame
,
xingBase
);
Seeker
seeker
;
if
(
seekHeader
==
SEEK_HEADER_XING
||
seekHeader
==
SEEK_HEADER_INFO
)
{
seeker
=
XingSeeker
.
create
(
synchronizedHeader
,
frame
,
input
.
getPosition
(),
input
.
getLength
());
if
(
seeker
!=
null
&&
!
gaplessInfoHolder
.
hasGaplessInfo
())
{
// If there is a Xing header, read gapless playback metadata at a fixed offset.
input
.
resetPeekPosition
();
...
...
@@ -377,31 +373,63 @@ public final class Mp3Extractor implements Extractor {
gaplessInfoHolder
.
setFromXingHeaderValue
(
scratch
.
readUnsignedInt24
());
}
input
.
skipFully
(
synchronizedHeader
.
frameSize
);
}
else
if
(
frame
.
limit
()
>=
40
)
{
// Check if there is a VBRI header.
frame
.
setPosition
(
36
);
// MPEG audio header (4 bytes) + 32 bytes.
headerData
=
frame
.
readInt
();
if
(
headerData
==
VBRI_HEADER
)
{
seeker
=
VbriSeeker
.
create
(
synchronizedHeader
,
frame
,
position
,
length
);
input
.
skipFully
(
synchronizedHeader
.
frameSize
);
if
(
seeker
!=
null
&&
!
seeker
.
isSeekable
()
&&
seekHeader
==
SEEK_HEADER_INFO
)
{
// Fall back to constant bitrate seeking for Info headers missing a table of contents.
return
getConstantBitrateSeeker
(
input
);
}
}
if
(
seeker
==
null
||
(!
seeker
.
isSeekable
()
&&
(
flags
&
FLAG_ENABLE_CONSTANT_BITRATE_SEEKING
)
!=
0
))
{
//
Repopulate the synchronized header in case we had to skip an invalid seeking header, which
// would give an invalid CBR bitrate.
}
else
if
(
seekHeader
==
SEEK_HEADER_VBRI
)
{
seeker
=
VbriSeeker
.
create
(
synchronizedHeader
,
frame
,
input
.
getPosition
(),
input
.
getLength
());
input
.
skipFully
(
synchronizedHeader
.
frameSize
);
}
else
{
// seekerHeader == SEEK_HEADER_UNSET
//
This frame doesn't contain seeking information, so reset the peek position.
seeker
=
null
;
input
.
resetPeekPosition
();
input
.
peekFully
(
scratch
.
data
,
0
,
4
);
scratch
.
setPosition
(
0
);
MpegAudioHeader
.
populateHeader
(
scratch
.
readInt
(),
synchronizedHeader
);
seeker
=
new
ConstantBitrateSeeker
(
input
.
getPosition
(),
synchronizedHeader
.
bitrate
,
length
);
}
return
seeker
;
}
/**
* Peeks the next frame and returns a {@link ConstantBitrateSeeker} based on its bitrate.
*/
private
Seeker
getConstantBitrateSeeker
(
ExtractorInput
input
)
throws
IOException
,
InterruptedException
{
input
.
peekFully
(
scratch
.
data
,
0
,
4
);
scratch
.
setPosition
(
0
);
MpegAudioHeader
.
populateHeader
(
scratch
.
readInt
(),
synchronizedHeader
);
return
new
ConstantBitrateSeeker
(
input
.
getPosition
(),
synchronizedHeader
.
bitrate
,
input
.
getLength
());
}
/**
* Returns whether the headers match in those bits masked by {@link #MPEG_AUDIO_HEADER_MASK}.
*/
private
static
boolean
headersMatch
(
int
headerA
,
long
headerB
)
{
return
(
headerA
&
MPEG_AUDIO_HEADER_MASK
)
==
(
headerB
&
MPEG_AUDIO_HEADER_MASK
);
}
/**
* Returns {@link #SEEK_HEADER_XING}, {@link #SEEK_HEADER_INFO} or {@link #SEEK_HEADER_VBRI} if
* the provided {@code frame} may have seeking metadata, or {@link #SEEK_HEADER_UNSET} otherwise.
* If seeking metadata is present, {@code frame}'s position is advanced past the header.
*/
private
static
int
getSeekFrameHeader
(
ParsableByteArray
frame
,
int
xingBase
)
{
if
(
frame
.
limit
()
>=
xingBase
+
4
)
{
frame
.
setPosition
(
xingBase
);
int
headerData
=
frame
.
readInt
();
if
(
headerData
==
SEEK_HEADER_XING
||
headerData
==
SEEK_HEADER_INFO
)
{
return
headerData
;
}
}
if
(
frame
.
limit
()
>=
40
)
{
frame
.
setPosition
(
36
);
// MPEG audio header (4 bytes) + 32 bytes.
if
(
frame
.
readInt
()
==
SEEK_HEADER_VBRI
)
{
return
SEEK_HEADER_VBRI
;
}
}
return
SEEK_HEADER_UNSET
;
}
/**
* {@link SeekMap} that also allows mapping from position (byte offset) back to time, which can be
* used to work out the new sample basis timestamp after seeking and resynchronization.
*/
...
...
library/core/src/main/java/com/google/android/exoplayer2/extractor/ts/TsExtractor.java
View file @
3ada4e17
...
...
@@ -382,10 +382,14 @@ public final class TsExtractor implements Extractor {
private
static
final
int
TS_PMT_DESC_DVBSUBS
=
0x59
;
private
final
ParsableBitArray
pmtScratch
;
private
final
SparseArray
<
TsPayloadReader
>
trackIdToReaderScratch
;
private
final
SparseIntArray
trackIdToPidScratch
;
private
final
int
pid
;
public
PmtReader
(
int
pid
)
{
pmtScratch
=
new
ParsableBitArray
(
new
byte
[
5
]);
trackIdToReaderScratch
=
new
SparseArray
<>();
trackIdToPidScratch
=
new
SparseIntArray
();
this
.
pid
=
pid
;
}
...
...
@@ -436,6 +440,8 @@ public final class TsExtractor implements Extractor {
new
TrackIdGenerator
(
programNumber
,
TS_STREAM_TYPE_ID3
,
MAX_PID_PLUS_ONE
));
}
trackIdToReaderScratch
.
clear
();
trackIdToPidScratch
.
clear
();
int
remainingEntriesLength
=
sectionData
.
bytesLeft
();
while
(
remainingEntriesLength
>
0
)
{
sectionData
.
readBytes
(
pmtScratch
,
5
);
...
...
@@ -454,23 +460,30 @@ public final class TsExtractor implements Extractor {
if
(
trackIds
.
get
(
trackId
))
{
continue
;
}
trackIds
.
put
(
trackId
,
true
);
TsPayloadReader
reader
;
if
(
mode
==
MODE_HLS
&&
streamType
==
TS_STREAM_TYPE_ID3
)
{
reader
=
id3Reader
;
}
else
{
reader
=
payloadReaderFactory
.
createPayloadReader
(
streamType
,
esInfo
);
if
(
reader
!=
null
)
{
reader
.
init
(
timestampAdjuster
,
output
,
new
TrackIdGenerator
(
programNumber
,
trackId
,
MAX_PID_PLUS_ONE
));
}
TsPayloadReader
reader
=
mode
==
MODE_HLS
&&
streamType
==
TS_STREAM_TYPE_ID3
?
id3Reader
:
payloadReaderFactory
.
createPayloadReader
(
streamType
,
esInfo
);
if
(
mode
!=
MODE_HLS
||
elementaryPid
<
trackIdToPidScratch
.
get
(
trackId
,
MAX_PID_PLUS_ONE
))
{
trackIdToPidScratch
.
put
(
trackId
,
elementaryPid
);
trackIdToReaderScratch
.
put
(
trackId
,
reader
);
}
}
int
trackIdCount
=
trackIdToPidScratch
.
size
();
for
(
int
i
=
0
;
i
<
trackIdCount
;
i
++)
{
int
trackId
=
trackIdToPidScratch
.
keyAt
(
i
);
trackIds
.
put
(
trackId
,
true
);
TsPayloadReader
reader
=
trackIdToReaderScratch
.
valueAt
(
i
);
if
(
reader
!=
null
)
{
tsPayloadReaders
.
put
(
elementaryPid
,
reader
);
if
(
reader
!=
id3Reader
)
{
reader
.
init
(
timestampAdjuster
,
output
,
new
TrackIdGenerator
(
programNumber
,
trackId
,
MAX_PID_PLUS_ONE
));
}
tsPayloadReaders
.
put
(
trackIdToPidScratch
.
valueAt
(
i
),
reader
);
}
}
if
(
mode
==
MODE_HLS
)
{
if
(!
tracksEnded
)
{
output
.
endTracks
();
...
...
library/core/src/main/java/com/google/android/exoplayer2/mediacodec/MediaCodecInfo.java
View file @
3ada4e17
...
...
@@ -71,7 +71,7 @@ public final class MediaCodecInfo {
* @return The created instance.
*/
public
static
MediaCodecInfo
newPassthroughInstance
(
String
name
)
{
return
new
MediaCodecInfo
(
name
,
null
,
null
);
return
new
MediaCodecInfo
(
name
,
null
,
null
,
false
);
}
/**
...
...
@@ -84,18 +84,29 @@ public final class MediaCodecInfo {
*/
public
static
MediaCodecInfo
newInstance
(
String
name
,
String
mimeType
,
CodecCapabilities
capabilities
)
{
return
new
MediaCodecInfo
(
name
,
mimeType
,
capabilities
);
return
new
MediaCodecInfo
(
name
,
mimeType
,
capabilities
,
false
);
}
/**
* @param name The name of the decoder.
* @param capabilities The capabilities of the decoder.
* Creates an instance.
*
* @param name The name of the {@link MediaCodec}.
* @param mimeType A mime type supported by the {@link MediaCodec}.
* @param capabilities The capabilities of the {@link MediaCodec} for the specified mime type.
* @param forceDisableAdaptive Whether {@link #adaptive} should be forced to {@code false}.
* @return The created instance.
*/
private
MediaCodecInfo
(
String
name
,
String
mimeType
,
CodecCapabilities
capabilities
)
{
public
static
MediaCodecInfo
newInstance
(
String
name
,
String
mimeType
,
CodecCapabilities
capabilities
,
boolean
forceDisableAdaptive
)
{
return
new
MediaCodecInfo
(
name
,
mimeType
,
capabilities
,
forceDisableAdaptive
);
}
private
MediaCodecInfo
(
String
name
,
String
mimeType
,
CodecCapabilities
capabilities
,
boolean
forceDisableAdaptive
)
{
this
.
name
=
Assertions
.
checkNotNull
(
name
);
this
.
mimeType
=
mimeType
;
this
.
capabilities
=
capabilities
;
adaptive
=
capabilities
!=
null
&&
isAdaptive
(
capabilities
);
adaptive
=
!
forceDisableAdaptive
&&
capabilities
!=
null
&&
isAdaptive
(
capabilities
);
tunneling
=
capabilities
!=
null
&&
isTunneling
(
capabilities
);
}
...
...
library/core/src/main/java/com/google/android/exoplayer2/mediacodec/MediaCodecRenderer.java
View file @
3ada4e17
...
...
@@ -339,7 +339,7 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
}
String
codecName
=
decoderInfo
.
name
;
codecIsAdaptive
=
decoderInfo
.
adaptive
&&
!
codecNeedsDisableAdaptationWorkaround
(
codecName
)
;
codecIsAdaptive
=
decoderInfo
.
adaptive
;
codecNeedsDiscardToSpsWorkaround
=
codecNeedsDiscardToSpsWorkaround
(
codecName
,
format
);
codecNeedsFlushWorkaround
=
codecNeedsFlushWorkaround
(
codecName
);
codecNeedsAdaptationWorkaround
=
codecNeedsAdaptationWorkaround
(
codecName
);
...
...
@@ -1188,18 +1188,4 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
&&
"OMX.MTK.AUDIO.DECODER.MP3"
.
equals
(
name
);
}
/**
* Returns whether the decoder is known to fail when adapting, despite advertising itself as an
* adaptive decoder.
* <p>
* If true is returned then we explicitly disable adaptation for the decoder.
*
* @param name The decoder name.
* @return True if the decoder is known to fail when adapting.
*/
private
static
boolean
codecNeedsDisableAdaptationWorkaround
(
String
name
)
{
return
Util
.
SDK_INT
<=
19
&&
Util
.
MODEL
.
equals
(
"ODROID-XU3"
)
&&
(
"OMX.Exynos.AVC.Decoder"
.
equals
(
name
)
||
"OMX.Exynos.AVC.Decoder.secure"
.
equals
(
name
));
}
}
library/core/src/main/java/com/google/android/exoplayer2/mediacodec/MediaCodecUtil.java
View file @
3ada4e17
...
...
@@ -160,6 +160,55 @@ public final class MediaCodecUtil {
return
decoderInfos
;
}
/**
* Returns the maximum frame size supported by the default H264 decoder.
*
* @return The maximum frame size for an H264 stream that can be decoded on the device.
*/
public
static
int
maxH264DecodableFrameSize
()
throws
DecoderQueryException
{
if
(
maxH264DecodableFrameSize
==
-
1
)
{
int
result
=
0
;
MediaCodecInfo
decoderInfo
=
getDecoderInfo
(
MimeTypes
.
VIDEO_H264
,
false
);
if
(
decoderInfo
!=
null
)
{
for
(
CodecProfileLevel
profileLevel
:
decoderInfo
.
getProfileLevels
())
{
result
=
Math
.
max
(
avcLevelToMaxFrameSize
(
profileLevel
.
level
),
result
);
}
// We assume support for at least 480p (SDK_INT >= 21) or 360p (SDK_INT < 21), which are
// the levels mandated by the Android CDD.
result
=
Math
.
max
(
result
,
Util
.
SDK_INT
>=
21
?
(
720
*
480
)
:
(
480
*
360
));
}
maxH264DecodableFrameSize
=
result
;
}
return
maxH264DecodableFrameSize
;
}
/**
* Returns profile and level (as defined by {@link CodecProfileLevel}) corresponding to the given
* codec description string (as defined by RFC 6381).
*
* @param codec A codec description string, as defined by RFC 6381.
* @return A pair (profile constant, level constant) if {@code codec} is well-formed and
* recognized, or null otherwise
*/
public
static
Pair
<
Integer
,
Integer
>
getCodecProfileAndLevel
(
String
codec
)
{
if
(
codec
==
null
)
{
return
null
;
}
String
[]
parts
=
codec
.
split
(
"\\."
);
switch
(
parts
[
0
])
{
case
CODEC_ID_HEV1:
case
CODEC_ID_HVC1:
return
getHevcProfileAndLevel
(
codec
,
parts
);
case
CODEC_ID_AVC1:
case
CODEC_ID_AVC2:
return
getAvcProfileAndLevel
(
codec
,
parts
);
default
:
return
null
;
}
}
// Internal methods.
private
static
List
<
MediaCodecInfo
>
getDecoderInfosInternal
(
CodecKey
key
,
MediaCodecListCompat
mediaCodecList
)
throws
DecoderQueryException
{
try
{
...
...
@@ -177,12 +226,14 @@ public final class MediaCodecUtil {
try
{
CodecCapabilities
capabilities
=
codecInfo
.
getCapabilitiesForType
(
supportedType
);
boolean
secure
=
mediaCodecList
.
isSecurePlaybackSupported
(
mimeType
,
capabilities
);
boolean
forceDisableAdaptive
=
codecNeedsDisableAdaptationWorkaround
(
codecName
);
if
((
secureDecodersExplicit
&&
key
.
secure
==
secure
)
||
(!
secureDecodersExplicit
&&
!
key
.
secure
))
{
decoderInfos
.
add
(
MediaCodecInfo
.
newInstance
(
codecName
,
mimeType
,
capabilities
));
decoderInfos
.
add
(
MediaCodecInfo
.
newInstance
(
codecName
,
mimeType
,
capabilities
,
forceDisableAdaptive
));
}
else
if
(!
secureDecodersExplicit
&&
secure
)
{
decoderInfos
.
add
(
MediaCodecInfo
.
newInstance
(
codecName
+
".secure"
,
mimeType
,
capabilities
));
capabilities
,
forceDisableAdaptive
));
// It only makes sense to have one synthesized secure decoder, return immediately.
return
decoderInfos
;
}
...
...
@@ -289,50 +340,16 @@ public final class MediaCodecUtil {
}
/**
* Returns the maximum frame size supported by the default H264 decoder.
* Returns whether the decoder is known to fail when adapting, despite advertising itself as an
* adaptive decoder.
*
* @return The maximum frame size for an H264 stream that can be decoded on the device.
* @param name The decoder name.
* @return True if the decoder is known to fail when adapting.
*/
public
static
int
maxH264DecodableFrameSize
()
throws
DecoderQueryException
{
if
(
maxH264DecodableFrameSize
==
-
1
)
{
int
result
=
0
;
MediaCodecInfo
decoderInfo
=
getDecoderInfo
(
MimeTypes
.
VIDEO_H264
,
false
);
if
(
decoderInfo
!=
null
)
{
for
(
CodecProfileLevel
profileLevel
:
decoderInfo
.
getProfileLevels
())
{
result
=
Math
.
max
(
avcLevelToMaxFrameSize
(
profileLevel
.
level
),
result
);
}
// We assume support for at least 480p (SDK_INT >= 21) or 360p (SDK_INT < 21), which are
// the levels mandated by the Android CDD.
result
=
Math
.
max
(
result
,
Util
.
SDK_INT
>=
21
?
(
720
*
480
)
:
(
480
*
360
));
}
maxH264DecodableFrameSize
=
result
;
}
return
maxH264DecodableFrameSize
;
}
/**
* Returns profile and level (as defined by {@link CodecProfileLevel}) corresponding to the given
* codec description string (as defined by RFC 6381).
*
* @param codec A codec description string, as defined by RFC 6381.
* @return A pair (profile constant, level constant) if {@code codec} is well-formed and
* recognized, or null otherwise
*/
public
static
Pair
<
Integer
,
Integer
>
getCodecProfileAndLevel
(
String
codec
)
{
if
(
codec
==
null
)
{
return
null
;
}
String
[]
parts
=
codec
.
split
(
"\\."
);
switch
(
parts
[
0
])
{
case
CODEC_ID_HEV1:
case
CODEC_ID_HVC1:
return
getHevcProfileAndLevel
(
codec
,
parts
);
case
CODEC_ID_AVC1:
case
CODEC_ID_AVC2:
return
getAvcProfileAndLevel
(
codec
,
parts
);
default
:
return
null
;
}
private
static
boolean
codecNeedsDisableAdaptationWorkaround
(
String
name
)
{
return
Util
.
SDK_INT
<=
22
&&
(
Util
.
MODEL
.
equals
(
"ODROID-XU3"
)
||
Util
.
MODEL
.
equals
(
"Nexus 10"
))
&&
(
"OMX.Exynos.AVC.Decoder"
.
equals
(
name
)
||
"OMX.Exynos.AVC.Decoder.secure"
.
equals
(
name
));
}
private
static
Pair
<
Integer
,
Integer
>
getHevcProfileAndLevel
(
String
codec
,
String
[]
parts
)
{
...
...
library/core/src/main/java/com/google/android/exoplayer2/trackselection/AdaptiveTrackSelection.java
View file @
3ada4e17
...
...
@@ -154,23 +154,24 @@ public class AdaptiveTrackSelection extends BaseTrackSelection {
@Override
public
void
updateSelectedTrack
(
long
bufferedDurationUs
)
{
long
nowMs
=
SystemClock
.
elapsedRealtime
();
//
Get the current and ideal selections
.
//
Stash the current selection, then make a new one
.
int
currentSelectedIndex
=
selectedIndex
;
Format
currentFormat
=
getSelectedFormat
();
int
idealSelectedIndex
=
determineIdealSelectedIndex
(
nowMs
);
Format
idealFormat
=
getFormat
(
idealSelectedIndex
);
// Assume we can switch to the ideal selection.
selectedIndex
=
idealSelectedIndex
;
// Revert back to the current selection if conditions are not suitable for switching.
if
(
currentFormat
!=
null
&&
!
isBlacklisted
(
selectedIndex
,
nowMs
))
{
if
(
idealFormat
.
bitrate
>
currentFormat
.
bitrate
selectedIndex
=
determineIdealSelectedIndex
(
nowMs
);
if
(
selectedIndex
==
currentSelectedIndex
)
{
return
;
}
if
(!
isBlacklisted
(
currentSelectedIndex
,
nowMs
))
{
// Revert back to the current selection if conditions are not suitable for switching.
Format
currentFormat
=
getFormat
(
currentSelectedIndex
);
Format
selectedFormat
=
getFormat
(
selectedIndex
);
if
(
selectedFormat
.
bitrate
>
currentFormat
.
bitrate
&&
bufferedDurationUs
<
minDurationForQualityIncreaseUs
)
{
// The
ideal
track is a higher quality, but we have insufficient buffer to safely switch
// The
selected
track is a higher quality, but we have insufficient buffer to safely switch
// up. Defer switching up for now.
selectedIndex
=
currentSelectedIndex
;
}
else
if
(
ideal
Format
.
bitrate
<
currentFormat
.
bitrate
}
else
if
(
selected
Format
.
bitrate
<
currentFormat
.
bitrate
&&
bufferedDurationUs
>=
maxDurationForQualityDecreaseUs
)
{
// The
ideal
track is a lower quality, but we have sufficient buffer to defer switching
// The
selected
track is a lower quality, but we have sufficient buffer to defer switching
// down for now.
selectedIndex
=
currentSelectedIndex
;
}
...
...
library/core/src/main/java/com/google/android/exoplayer2/trackselection/DefaultTrackSelector.java
View file @
3ada4e17
...
...
@@ -436,35 +436,48 @@ public class DefaultTrackSelector extends MappingTrackSelector {
int
rendererCount
=
rendererCapabilities
.
length
;
TrackSelection
[]
rendererTrackSelections
=
new
TrackSelection
[
rendererCount
];
Parameters
params
=
paramsReference
.
get
();
boolean
videoTrackAndRendererPresent
=
false
;
boolean
seenVideoRendererWithMappedTracks
=
false
;
boolean
selectedVideoTracks
=
false
;
for
(
int
i
=
0
;
i
<
rendererCount
;
i
++)
{
if
(
C
.
TRACK_TYPE_VIDEO
==
rendererCapabilities
[
i
].
getTrackType
())
{
rendererTrackSelections
[
i
]
=
selectVideoTrack
(
rendererCapabilities
[
i
],
rendererTrackGroupArrays
[
i
],
rendererFormatSupports
[
i
],
params
.
maxVideoWidth
,
params
.
maxVideoHeight
,
params
.
maxVideoBitrate
,
params
.
allowNonSeamlessAdaptiveness
,
params
.
allowMixedMimeAdaptiveness
,
params
.
viewportWidth
,
params
.
viewportHeight
,
params
.
orientationMayChange
,
adaptiveTrackSelectionFactory
,
params
.
exceedVideoConstraintsIfNecessary
,
params
.
exceedRendererCapabilitiesIfNecessary
);
videoTrackAndRendererPresent
|=
rendererTrackGroupArrays
[
i
].
length
>
0
;
if
(!
selectedVideoTracks
)
{
rendererTrackSelections
[
i
]
=
selectVideoTrack
(
rendererCapabilities
[
i
],
rendererTrackGroupArrays
[
i
],
rendererFormatSupports
[
i
],
params
.
maxVideoWidth
,
params
.
maxVideoHeight
,
params
.
maxVideoBitrate
,
params
.
allowNonSeamlessAdaptiveness
,
params
.
allowMixedMimeAdaptiveness
,
params
.
viewportWidth
,
params
.
viewportHeight
,
params
.
orientationMayChange
,
adaptiveTrackSelectionFactory
,
params
.
exceedVideoConstraintsIfNecessary
,
params
.
exceedRendererCapabilitiesIfNecessary
);
selectedVideoTracks
=
rendererTrackSelections
[
i
]
!=
null
;
}
seenVideoRendererWithMappedTracks
|=
rendererTrackGroupArrays
[
i
].
length
>
0
;
}
}
boolean
selectedAudioTracks
=
false
;
boolean
selectedTextTracks
=
false
;
for
(
int
i
=
0
;
i
<
rendererCount
;
i
++)
{
switch
(
rendererCapabilities
[
i
].
getTrackType
())
{
case
C
.
TRACK_TYPE_VIDEO
:
// Already done. Do nothing.
break
;
case
C
.
TRACK_TYPE_AUDIO
:
rendererTrackSelections
[
i
]
=
selectAudioTrack
(
rendererTrackGroupArrays
[
i
],
rendererFormatSupports
[
i
],
params
.
preferredAudioLanguage
,
params
.
exceedRendererCapabilitiesIfNecessary
,
params
.
allowMixedMimeAdaptiveness
,
videoTrackAndRendererPresent
?
null
:
adaptiveTrackSelectionFactory
);
if
(!
selectedAudioTracks
)
{
rendererTrackSelections
[
i
]
=
selectAudioTrack
(
rendererTrackGroupArrays
[
i
],
rendererFormatSupports
[
i
],
params
.
preferredAudioLanguage
,
params
.
exceedRendererCapabilitiesIfNecessary
,
params
.
allowMixedMimeAdaptiveness
,
seenVideoRendererWithMappedTracks
?
null
:
adaptiveTrackSelectionFactory
);
selectedAudioTracks
=
rendererTrackSelections
[
i
]
!=
null
;
}
break
;
case
C
.
TRACK_TYPE_TEXT
:
rendererTrackSelections
[
i
]
=
selectTextTrack
(
rendererTrackGroupArrays
[
i
],
rendererFormatSupports
[
i
],
params
.
preferredTextLanguage
,
params
.
preferredAudioLanguage
,
params
.
exceedRendererCapabilitiesIfNecessary
);
if
(!
selectedTextTracks
)
{
rendererTrackSelections
[
i
]
=
selectTextTrack
(
rendererTrackGroupArrays
[
i
],
rendererFormatSupports
[
i
],
params
.
preferredTextLanguage
,
params
.
preferredAudioLanguage
,
params
.
exceedRendererCapabilitiesIfNecessary
);
selectedTextTracks
=
rendererTrackSelections
[
i
]
!=
null
;
}
break
;
default
:
rendererTrackSelections
[
i
]
=
selectOtherTrack
(
rendererCapabilities
[
i
].
getTrackType
(),
...
...
@@ -626,7 +639,8 @@ public class DefaultTrackSelector extends MappingTrackSelector {
continue
;
}
int
trackScore
=
isWithinConstraints
?
2
:
1
;
if
(
isSupported
(
trackFormatSupport
[
trackIndex
],
false
))
{
boolean
isWithinCapabilities
=
isSupported
(
trackFormatSupport
[
trackIndex
],
false
);
if
(
isWithinCapabilities
)
{
trackScore
+=
WITHIN_RENDERER_CAPABILITIES_BONUS
;
}
boolean
selectTrack
=
trackScore
>
selectedTrackScore
;
...
...
@@ -642,7 +656,8 @@ public class DefaultTrackSelector extends MappingTrackSelector {
}
else
{
comparisonResult
=
compareFormatValues
(
format
.
bitrate
,
selectedBitrate
);
}
selectTrack
=
isWithinConstraints
?
comparisonResult
>
0
:
comparisonResult
<
0
;
selectTrack
=
isWithinCapabilities
&&
isWithinConstraints
?
comparisonResult
>
0
:
comparisonResult
<
0
;
}
if
(
selectTrack
)
{
selectedGroup
=
trackGroup
;
...
...
library/core/src/main/java/com/google/android/exoplayer2/util/MimeTypes.java
View file @
3ada4e17
...
...
@@ -61,6 +61,7 @@ public final class MimeTypes {
public
static
final
String
AUDIO_AMR_WB
=
BASE_TYPE_AUDIO
+
"/amr-wb"
;
public
static
final
String
AUDIO_FLAC
=
BASE_TYPE_AUDIO
+
"/x-flac"
;
public
static
final
String
AUDIO_ALAC
=
BASE_TYPE_AUDIO
+
"/alac"
;
public
static
final
String
AUDIO_UNKNOWN
=
BASE_TYPE_AUDIO
+
"/x-unknown"
;
public
static
final
String
TEXT_VTT
=
BASE_TYPE_TEXT
+
"/vtt"
;
...
...
library/core/src/main/java/com/google/android/exoplayer2/util/Util.java
View file @
3ada4e17
...
...
@@ -98,7 +98,7 @@ public final class Util {
private
static
final
Pattern
XS_DATE_TIME_PATTERN
=
Pattern
.
compile
(
"(\\d\\d\\d\\d)\\-(\\d\\d)\\-(\\d\\d)[Tt]"
+
"(\\d\\d):(\\d\\d):(\\d\\d)([\\.,](\\d+))?"
+
"([Zz]|((\\+|\\-)(\\d\\d):?(\\d\\d)))?"
);
+
"([Zz]|((\\+|\\-)(\\d
?
\\d):?(\\d\\d)))?"
);
private
static
final
Pattern
XS_DURATION_PATTERN
=
Pattern
.
compile
(
"^(-)?P(([0-9]*)Y)?(([0-9]*)M)?(([0-9]*)D)?"
+
"(T(([0-9]*)H)?(([0-9]*)M)?(([0-9.]*)S)?)?$"
);
...
...
library/core/src/main/java/com/google/android/exoplayer2/video/DummySurface.java
View file @
3ada4e17
...
...
@@ -76,7 +76,7 @@ public final class DummySurface extends Surface {
if
(
Util
.
SDK_INT
>=
17
)
{
EGLDisplay
display
=
eglGetDisplay
(
EGL_DEFAULT_DISPLAY
);
String
extensions
=
EGL14
.
eglQueryString
(
display
,
EGL10
.
EGL_EXTENSIONS
);
SECURE_SUPPORTED
=
extensions
.
contains
(
"EGL_EXT_protected_content"
);
SECURE_SUPPORTED
=
extensions
!=
null
&&
extensions
.
contains
(
"EGL_EXT_protected_content"
);
}
else
{
SECURE_SUPPORTED
=
false
;
}
...
...
library/core/src/main/java/com/google/android/exoplayer2/video/MediaCodecVideoRenderer.java
View file @
3ada4e17
...
...
@@ -650,7 +650,7 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer {
* @return Suitable {@link CodecMaxValues}.
* @throws DecoderQueryException If an error occurs querying {@code codecInfo}.
*/
pr
ivate
static
CodecMaxValues
getCodecMaxValues
(
MediaCodecInfo
codecInfo
,
Format
format
,
pr
otected
CodecMaxValues
getCodecMaxValues
(
MediaCodecInfo
codecInfo
,
Format
format
,
Format
[]
streamFormats
)
throws
DecoderQueryException
{
int
maxWidth
=
format
.
width
;
int
maxHeight
=
format
.
height
;
...
...
@@ -838,7 +838,7 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer {
return
format
.
rotationDegrees
==
Format
.
NO_VALUE
?
0
:
format
.
rotationDegrees
;
}
pr
ivate
static
final
class
CodecMaxValues
{
pr
otected
static
final
class
CodecMaxValues
{
public
final
int
width
;
public
final
int
height
;
...
...
library/dash/src/main/java/com/google/android/exoplayer2/source/dash/DashMediaSource.java
View file @
3ada4e17
...
...
@@ -410,12 +410,14 @@ public final class DashMediaSource implements MediaSource {
private
void
resolveUtcTimingElement
(
UtcTimingElement
timingElement
)
{
String
scheme
=
timingElement
.
schemeIdUri
;
if
(
Util
.
areEqual
(
scheme
,
"urn:mpeg:dash:utc:direct:2012"
))
{
if
(
Util
.
areEqual
(
scheme
,
"urn:mpeg:dash:utc:direct:2014"
)
||
Util
.
areEqual
(
scheme
,
"urn:mpeg:dash:utc:direct:2012"
))
{
resolveUtcTimingElementDirect
(
timingElement
);
}
else
if
(
Util
.
areEqual
(
scheme
,
"urn:mpeg:dash:utc:http-iso:2014"
))
{
}
else
if
(
Util
.
areEqual
(
scheme
,
"urn:mpeg:dash:utc:http-iso:2014"
)
||
Util
.
areEqual
(
scheme
,
"urn:mpeg:dash:utc:http-iso:2012"
))
{
resolveUtcTimingElementHttp
(
timingElement
,
new
Iso8601Parser
());
}
else
if
(
Util
.
areEqual
(
scheme
,
"urn:mpeg:dash:utc:http-xsdate:201
2
"
)
||
Util
.
areEqual
(
scheme
,
"urn:mpeg:dash:utc:http-xsdate:201
4
"
))
{
}
else
if
(
Util
.
areEqual
(
scheme
,
"urn:mpeg:dash:utc:http-xsdate:201
4
"
)
||
Util
.
areEqual
(
scheme
,
"urn:mpeg:dash:utc:http-xsdate:201
2
"
))
{
resolveUtcTimingElementHttp
(
timingElement
,
new
XsDateTimeParser
());
}
else
{
// Unsupported scheme.
...
...
library/hls/src/androidTest/java/com/google/android/exoplayer2/source/hls/playlist/HlsMasterPlaylistParserTest.java
View file @
3ada4e17
...
...
@@ -51,6 +51,15 @@ public class HlsMasterPlaylistParserTest extends TestCase {
+
"#EXT-X-STREAM-INF:BANDWIDTH=65000,CODECS=\"mp4a.40.5\"\n"
+
"http://example.com/audio-only.m3u8"
;
private
static
final
String
AVG_BANDWIDTH_MASTER_PLAYLIST
=
" #EXTM3U \n"
+
"\n"
+
"#EXT-X-STREAM-INF:BANDWIDTH=1280000,CODECS=\"mp4a.40.2,avc1.66.30\",RESOLUTION=304x128\n"
+
"http://example.com/low.m3u8\n"
+
"\n"
+
"#EXT-X-STREAM-INF:BANDWIDTH=1280000,AVERAGE-BANDWIDTH=1270000,"
+
"CODECS=\"mp4a.40.2 , avc1.66.30 \"\n"
+
"http://example.com/spaces_in_codecs.m3u8\n"
;
private
static
final
String
PLAYLIST_WITH_INVALID_HEADER
=
"#EXTMU3\n"
+
"#EXT-X-STREAM-INF:BANDWIDTH=1280000,CODECS=\"mp4a.40.2,avc1.66.30\",RESOLUTION=304x128\n"
+
"http://example.com/low.m3u8\n"
;
...
...
@@ -70,42 +79,48 @@ public class HlsMasterPlaylistParserTest extends TestCase {
HlsMasterPlaylist
masterPlaylist
=
parseMasterPlaylist
(
PLAYLIST_URI
,
MASTER_PLAYLIST
);
List
<
HlsMasterPlaylist
.
HlsUrl
>
variants
=
masterPlaylist
.
variants
;
assertNotNull
(
variants
);
assertEquals
(
5
,
variants
.
size
());
assertNull
(
masterPlaylist
.
muxedCaptionFormats
);
assertEquals
(
1280000
,
variants
.
get
(
0
).
format
.
bitrate
);
assertNotNull
(
variants
.
get
(
0
).
format
.
codecs
);
assertEquals
(
"mp4a.40.2,avc1.66.30"
,
variants
.
get
(
0
).
format
.
codecs
);
assertEquals
(
304
,
variants
.
get
(
0
).
format
.
width
);
assertEquals
(
128
,
variants
.
get
(
0
).
format
.
height
);
assertEquals
(
"http://example.com/low.m3u8"
,
variants
.
get
(
0
).
url
);
assertEquals
(
1280000
,
variants
.
get
(
1
).
format
.
bitrate
);
assertNotNull
(
variants
.
get
(
1
).
format
.
codecs
);
assertEquals
(
"mp4a.40.2 , avc1.66.30 "
,
variants
.
get
(
1
).
format
.
codecs
);
assertEquals
(
"http://example.com/spaces_in_codecs.m3u8"
,
variants
.
get
(
1
).
url
);
assertEquals
(
2560000
,
variants
.
get
(
2
).
format
.
bitrate
);
assert
Equals
(
null
,
variants
.
get
(
2
).
format
.
codecs
);
assert
Null
(
variants
.
get
(
2
).
format
.
codecs
);
assertEquals
(
384
,
variants
.
get
(
2
).
format
.
width
);
assertEquals
(
160
,
variants
.
get
(
2
).
format
.
height
);
assertEquals
(
"http://example.com/mid.m3u8"
,
variants
.
get
(
2
).
url
);
assertEquals
(
7680000
,
variants
.
get
(
3
).
format
.
bitrate
);
assert
Equals
(
null
,
variants
.
get
(
3
).
format
.
codecs
);
assert
Null
(
variants
.
get
(
3
).
format
.
codecs
);
assertEquals
(
Format
.
NO_VALUE
,
variants
.
get
(
3
).
format
.
width
);
assertEquals
(
Format
.
NO_VALUE
,
variants
.
get
(
3
).
format
.
height
);
assertEquals
(
"http://example.com/hi.m3u8"
,
variants
.
get
(
3
).
url
);
assertEquals
(
65000
,
variants
.
get
(
4
).
format
.
bitrate
);
assertNotNull
(
variants
.
get
(
4
).
format
.
codecs
);
assertEquals
(
"mp4a.40.5"
,
variants
.
get
(
4
).
format
.
codecs
);
assertEquals
(
Format
.
NO_VALUE
,
variants
.
get
(
4
).
format
.
width
);
assertEquals
(
Format
.
NO_VALUE
,
variants
.
get
(
4
).
format
.
height
);
assertEquals
(
"http://example.com/audio-only.m3u8"
,
variants
.
get
(
4
).
url
);
}
public
void
testMasterPlaylistWithBandwdithAverage
()
throws
IOException
{
HlsMasterPlaylist
masterPlaylist
=
parseMasterPlaylist
(
PLAYLIST_URI
,
AVG_BANDWIDTH_MASTER_PLAYLIST
);
List
<
HlsMasterPlaylist
.
HlsUrl
>
variants
=
masterPlaylist
.
variants
;
assertEquals
(
1280000
,
variants
.
get
(
0
).
format
.
bitrate
);
assertEquals
(
1270000
,
variants
.
get
(
1
).
format
.
bitrate
);
}
public
void
testPlaylistWithInvalidHeader
()
throws
IOException
{
try
{
parseMasterPlaylist
(
PLAYLIST_URI
,
PLAYLIST_WITH_INVALID_HEADER
);
...
...
library/hls/src/main/java/com/google/android/exoplayer2/source/hls/playlist/HlsPlaylistParser.java
View file @
3ada4e17
...
...
@@ -73,7 +73,9 @@ public final class HlsPlaylistParser implements ParsingLoadable.Parser<HlsPlayli
private
static
final
String
ATTR_CLOSED_CAPTIONS_NONE
=
"CLOSED-CAPTIONS=NONE"
;
private
static
final
Pattern
REGEX_BANDWIDTH
=
Pattern
.
compile
(
"BANDWIDTH=(\\d+)\\b"
);
private
static
final
Pattern
REGEX_AVERAGE_BANDWIDTH
=
Pattern
.
compile
(
"AVERAGE-BANDWIDTH=(\\d+)\\b"
);
private
static
final
Pattern
REGEX_BANDWIDTH
=
Pattern
.
compile
(
"[^-]BANDWIDTH=(\\d+)\\b"
);
private
static
final
Pattern
REGEX_CODECS
=
Pattern
.
compile
(
"CODECS=\"(.+?)\""
);
private
static
final
Pattern
REGEX_RESOLUTION
=
Pattern
.
compile
(
"RESOLUTION=(\\d+x\\d+)"
);
private
static
final
Pattern
REGEX_TARGET_DURATION
=
Pattern
.
compile
(
TAG_TARGET_DURATION
...
...
@@ -226,6 +228,11 @@ public final class HlsPlaylistParser implements ParsingLoadable.Parser<HlsPlayli
}
}
else
if
(
line
.
startsWith
(
TAG_STREAM_INF
))
{
int
bitrate
=
parseIntAttr
(
line
,
REGEX_BANDWIDTH
);
String
averageBandwidthString
=
parseOptionalStringAttr
(
line
,
REGEX_AVERAGE_BANDWIDTH
);
if
(
averageBandwidthString
!=
null
)
{
// If available, the average bandwidth attribute is used as the variant's bitrate.
bitrate
=
Integer
.
parseInt
(
averageBandwidthString
);
}
String
codecs
=
parseOptionalStringAttr
(
line
,
REGEX_CODECS
);
String
resolutionString
=
parseOptionalStringAttr
(
line
,
REGEX_RESOLUTION
);
noClosedCaptions
|=
line
.
contains
(
ATTR_CLOSED_CAPTIONS_NONE
);
...
...
@@ -300,8 +307,6 @@ public final class HlsPlaylistParser implements ParsingLoadable.Parser<HlsPlayli
playlistType
=
HlsMediaPlaylist
.
PLAYLIST_TYPE_VOD
;
}
else
if
(
"EVENT"
.
equals
(
playlistTypeString
))
{
playlistType
=
HlsMediaPlaylist
.
PLAYLIST_TYPE_EVENT
;
}
else
{
throw
new
ParserException
(
"Illegal playlist type: "
+
playlistTypeString
);
}
}
else
if
(
line
.
startsWith
(
TAG_START
))
{
startOffsetUs
=
(
long
)
(
parseDoubleAttr
(
line
,
REGEX_TIME_OFFSET
)
*
C
.
MICROS_PER_SECOND
);
...
...
@@ -390,14 +395,6 @@ public final class HlsPlaylistParser implements ParsingLoadable.Parser<HlsPlayli
dateRanges
);
}
private
static
String
parseStringAttr
(
String
line
,
Pattern
pattern
)
throws
ParserException
{
Matcher
matcher
=
pattern
.
matcher
(
line
);
if
(
matcher
.
find
()
&&
matcher
.
groupCount
()
==
1
)
{
return
matcher
.
group
(
1
);
}
throw
new
ParserException
(
"Couldn't match "
+
pattern
.
pattern
()
+
" in "
+
line
);
}
private
static
int
parseIntAttr
(
String
line
,
Pattern
pattern
)
throws
ParserException
{
return
Integer
.
parseInt
(
parseStringAttr
(
line
,
pattern
));
}
...
...
@@ -408,10 +405,15 @@ public final class HlsPlaylistParser implements ParsingLoadable.Parser<HlsPlayli
private
static
String
parseOptionalStringAttr
(
String
line
,
Pattern
pattern
)
{
Matcher
matcher
=
pattern
.
matcher
(
line
);
if
(
matcher
.
find
())
{
return
matcher
.
find
()
?
matcher
.
group
(
1
)
:
null
;
}
private
static
String
parseStringAttr
(
String
line
,
Pattern
pattern
)
throws
ParserException
{
Matcher
matcher
=
pattern
.
matcher
(
line
);
if
(
matcher
.
find
()
&&
matcher
.
groupCount
()
==
1
)
{
return
matcher
.
group
(
1
);
}
return
null
;
throw
new
ParserException
(
"Couldn't match "
+
pattern
.
pattern
()
+
" in "
+
line
)
;
}
private
static
boolean
parseBooleanAttribute
(
String
line
,
Pattern
pattern
,
boolean
defaultValue
)
{
...
...
library/smoothstreaming/src/main/java/com/google/android/exoplayer2/source/smoothstreaming/SsMediaSource.java
View file @
3ada4e17
...
...
@@ -287,39 +287,41 @@ public final class SsMediaSource implements MediaSource,
for
(
int
i
=
0
;
i
<
mediaPeriods
.
size
();
i
++)
{
mediaPeriods
.
get
(
i
).
updateManifest
(
manifest
);
}
long
startTimeUs
=
Long
.
MAX_VALUE
;
long
endTimeUs
=
Long
.
MIN_VALUE
;
for
(
StreamElement
element
:
manifest
.
streamElements
)
{
if
(
element
.
chunkCount
>
0
)
{
startTimeUs
=
Math
.
min
(
startTimeUs
,
element
.
getStartTimeUs
(
0
));
endTimeUs
=
Math
.
max
(
endTimeUs
,
element
.
getStartTimeUs
(
element
.
chunkCount
-
1
)
+
element
.
getChunkDurationUs
(
element
.
chunkCount
-
1
));
}
}
Timeline
timeline
;
if
(
manifest
.
isLive
)
{
long
startTimeUs
=
Long
.
MAX_VALUE
;
long
endTimeUs
=
Long
.
MIN_VALUE
;
for
(
int
i
=
0
;
i
<
manifest
.
streamElements
.
length
;
i
++)
{
StreamElement
element
=
manifest
.
streamElements
[
i
];
if
(
element
.
chunkCount
>
0
)
{
startTimeUs
=
Math
.
min
(
startTimeUs
,
element
.
getStartTimeUs
(
0
));
endTimeUs
=
Math
.
max
(
endTimeUs
,
element
.
getStartTimeUs
(
element
.
chunkCount
-
1
)
+
element
.
getChunkDurationUs
(
element
.
chunkCount
-
1
));
}
if
(
startTimeUs
==
Long
.
MAX_VALUE
)
{
long
periodDurationUs
=
manifest
.
isLive
?
C
.
TIME_UNSET
:
0
;
timeline
=
new
SinglePeriodTimeline
(
periodDurationUs
,
0
,
0
,
0
,
true
/* isSeekable */
,
manifest
.
isLive
/* isDynamic */
);
}
else
if
(
manifest
.
isLive
)
{
if
(
manifest
.
dvrWindowLengthUs
!=
C
.
TIME_UNSET
&&
manifest
.
dvrWindowLengthUs
>
0
)
{
startTimeUs
=
Math
.
max
(
startTimeUs
,
endTimeUs
-
manifest
.
dvrWindowLengthUs
);
}
if
(
startTimeUs
==
Long
.
MAX_VALUE
)
{
timeline
=
new
SinglePeriodTimeline
(
C
.
TIME_UNSET
,
false
);
}
else
{
if
(
manifest
.
dvrWindowLengthUs
!=
C
.
TIME_UNSET
&&
manifest
.
dvrWindowLengthUs
>
0
)
{
startTimeUs
=
Math
.
max
(
startTimeUs
,
endTimeUs
-
manifest
.
dvrWindowLengthUs
);
}
long
durationUs
=
endTimeUs
-
startTimeUs
;
long
defaultStartPositionUs
=
durationUs
-
C
.
msToUs
(
livePresentationDelayMs
);
if
(
defaultStartPositionUs
<
MIN_LIVE_DEFAULT_START_POSITION_US
)
{
// The default start position is too close to the start of the live window. Set it to the
// minimum default start position provided the window is at least twice as big. Else set
// it to the middle of the window.
defaultStartPositionUs
=
Math
.
min
(
MIN_LIVE_DEFAULT_START_POSITION_US
,
durationUs
/
2
);
}
timeline
=
new
SinglePeriodTimeline
(
C
.
TIME_UNSET
,
durationUs
,
startTimeUs
,
defaultStartPositionUs
,
true
/* isSeekable */
,
true
/* isDynamic */
);
long
durationUs
=
endTimeUs
-
startTimeUs
;
long
defaultStartPositionUs
=
durationUs
-
C
.
msToUs
(
livePresentationDelayMs
);
if
(
defaultStartPositionUs
<
MIN_LIVE_DEFAULT_START_POSITION_US
)
{
// The default start position is too close to the start of the live window. Set it to the
// minimum default start position provided the window is at least twice as big. Else set
// it to the middle of the window.
defaultStartPositionUs
=
Math
.
min
(
MIN_LIVE_DEFAULT_START_POSITION_US
,
durationUs
/
2
);
}
timeline
=
new
SinglePeriodTimeline
(
C
.
TIME_UNSET
,
durationUs
,
startTimeUs
,
defaultStartPositionUs
,
true
/* isSeekable */
,
true
/* isDynamic */
);
}
else
{
boolean
isSeekable
=
manifest
.
durationUs
!=
C
.
TIME_UNSET
;
timeline
=
new
SinglePeriodTimeline
(
manifest
.
durationUs
,
isSeekable
);
long
durationUs
=
manifest
.
durationUs
!=
C
.
TIME_UNSET
?
manifest
.
durationUs
:
endTimeUs
-
startTimeUs
;
timeline
=
new
SinglePeriodTimeline
(
startTimeUs
+
durationUs
,
durationUs
,
startTimeUs
,
0
,
true
/* isSeekable */
,
false
/* isDynamic */
);
}
sourceListener
.
onSourceInfoRefreshed
(
timeline
,
manifest
);
}
...
...
library/ui/src/main/java/com/google/android/exoplayer2/ui/DefaultTimeBar.java
View file @
3ada4e17
...
...
@@ -220,11 +220,13 @@ public class DefaultTimeBar extends View implements TimeBar {
public
void
setPosition
(
long
position
)
{
this
.
position
=
position
;
setContentDescription
(
getProgressText
());
update
();
}
@Override
public
void
setBufferedPosition
(
long
bufferedPosition
)
{
this
.
bufferedPosition
=
bufferedPosition
;
update
();
}
@Override
...
...
@@ -235,6 +237,7 @@ public class DefaultTimeBar extends View implements TimeBar {
}
else
{
updateScrubberState
();
}
update
();
}
@Override
...
...
@@ -242,6 +245,7 @@ public class DefaultTimeBar extends View implements TimeBar {
Assertions
.
checkArgument
(
adBreakCount
==
0
||
adBreakTimesMs
!=
null
);
this
.
adBreakCount
=
adBreakCount
;
this
.
adBreakTimesMs
=
adBreakTimesMs
;
update
();
}
@Override
...
...
@@ -438,7 +442,7 @@ public class DefaultTimeBar extends View implements TimeBar {
parent
.
requestDisallowInterceptTouchEvent
(
true
);
}
if
(
listener
!=
null
)
{
listener
.
onScrubStart
(
this
);
listener
.
onScrubStart
(
this
,
getScrubberPosition
()
);
}
}
...
...
library/ui/src/main/java/com/google/android/exoplayer2/ui/PlaybackControlView.java
View file @
3ada4e17
...
...
@@ -875,7 +875,7 @@ public class PlaybackControlView extends FrameLayout {
OnClickListener
{
@Override
public
void
onScrubStart
(
TimeBar
timeBar
)
{
public
void
onScrubStart
(
TimeBar
timeBar
,
long
position
)
{
removeCallbacks
(
hideAction
);
scrubbing
=
true
;
}
...
...
library/ui/src/main/java/com/google/android/exoplayer2/ui/TimeBar.java
View file @
3ada4e17
...
...
@@ -95,8 +95,9 @@ public interface TimeBar {
* Called when the user starts moving the scrubber.
*
* @param timeBar The time bar.
* @param position The position of the scrubber, in milliseconds.
*/
void
onScrubStart
(
TimeBar
timeBar
);
void
onScrubStart
(
TimeBar
timeBar
,
long
position
);
/**
* Called when the user moves the scrubber.
...
...
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