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
ced03e9a
authored
Oct 27, 2016
by
ojw28
Committed by
GitHub
Oct 27, 2016
Browse files
Options
_('Browse Files')
Download
Plain Diff
Merge pull request #2008 from google/dev-v2-id3
Merge ID3 support into dev-v2
parents
2c543632
c4b4e845
Show whitespace changes
Inline
Side-by-side
Showing
32 changed files
with
1667 additions
and
579 deletions
demo/src/main/java/com/google/android/exoplayer2/demo/EventLogger.java
library/src/androidTest/java/com/google/android/exoplayer2/FormatTest.java
library/src/androidTest/java/com/google/android/exoplayer2/metadata/id3/Id3DecoderTest.java
library/src/main/java/com/google/android/exoplayer2/Format.java
library/src/main/java/com/google/android/exoplayer2/SimpleExoPlayer.java
library/src/main/java/com/google/android/exoplayer2/extractor/GaplessInfoHolder.java
library/src/main/java/com/google/android/exoplayer2/extractor/mp3/Id3Util.java
library/src/main/java/com/google/android/exoplayer2/extractor/mp3/Mp3Extractor.java
library/src/main/java/com/google/android/exoplayer2/extractor/mp4/Atom.java
library/src/main/java/com/google/android/exoplayer2/extractor/mp4/AtomParsers.java
library/src/main/java/com/google/android/exoplayer2/extractor/mp4/MetadataUtil.java
library/src/main/java/com/google/android/exoplayer2/extractor/mp4/Mp4Extractor.java
library/src/main/java/com/google/android/exoplayer2/metadata/Metadata.java
library/src/main/java/com/google/android/exoplayer2/metadata/MetadataDecoder.java
library/src/main/java/com/google/android/exoplayer2/metadata/MetadataRenderer.java
library/src/main/java/com/google/android/exoplayer2/metadata/id3/ApicFrame.java
library/src/main/java/com/google/android/exoplayer2/metadata/id3/BinaryFrame.java
library/src/main/java/com/google/android/exoplayer2/metadata/id3/CommentFrame.java
library/src/main/java/com/google/android/exoplayer2/metadata/id3/GeobFrame.java
library/src/main/java/com/google/android/exoplayer2/metadata/id3/Id3Decoder.java
library/src/main/java/com/google/android/exoplayer2/metadata/id3/Id3Frame.java
library/src/main/java/com/google/android/exoplayer2/metadata/id3/PrivFrame.java
library/src/main/java/com/google/android/exoplayer2/metadata/id3/TextInformationFrame.java
library/src/main/java/com/google/android/exoplayer2/metadata/id3/TxxxFrame.java
library/src/main/java/com/google/android/exoplayer2/ui/AspectRatioFrameLayout.java
library/src/main/java/com/google/android/exoplayer2/ui/PlaybackControlView.java
library/src/main/java/com/google/android/exoplayer2/ui/SimpleExoPlayerView.java
library/src/main/java/com/google/android/exoplayer2/util/ParsableByteArray.java
library/src/main/res/layout/exo_playback_control_view.xml
library/src/main/res/layout/exo_simple_player_view.xml
library/src/main/res/values/attrs.xml
library/src/main/res/values/ids.xml
demo/src/main/java/com/google/android/exoplayer2/demo/EventLogger.java
View file @
ced03e9a
...
...
@@ -27,8 +27,10 @@ import com.google.android.exoplayer2.Timeline;
import
com.google.android.exoplayer2.audio.AudioRendererEventListener
;
import
com.google.android.exoplayer2.decoder.DecoderCounters
;
import
com.google.android.exoplayer2.drm.StreamingDrmSessionManager
;
import
com.google.android.exoplayer2.metadata.Metadata
;
import
com.google.android.exoplayer2.metadata.MetadataRenderer
;
import
com.google.android.exoplayer2.metadata.id3.ApicFrame
;
import
com.google.android.exoplayer2.metadata.id3.CommentFrame
;
import
com.google.android.exoplayer2.metadata.id3.GeobFrame
;
import
com.google.android.exoplayer2.metadata.id3.Id3Frame
;
import
com.google.android.exoplayer2.metadata.id3.PrivFrame
;
...
...
@@ -46,7 +48,6 @@ import com.google.android.exoplayer2.upstream.DataSpec;
import
com.google.android.exoplayer2.video.VideoRendererEventListener
;
import
java.io.IOException
;
import
java.text.NumberFormat
;
import
java.util.List
;
import
java.util.Locale
;
/**
...
...
@@ -55,7 +56,7 @@ import java.util.Locale;
/* package */
final
class
EventLogger
implements
ExoPlayer
.
EventListener
,
AudioRendererEventListener
,
VideoRendererEventListener
,
AdaptiveMediaSourceEventListener
,
ExtractorMediaSource
.
EventListener
,
StreamingDrmSessionManager
.
EventListener
,
MetadataRenderer
.
Output
<
List
<
Id3Frame
>>
{
MetadataRenderer
.
Output
{
private
static
final
String
TAG
=
"EventLogger"
;
private
static
final
int
MAX_TIMELINE_ITEM_LINES
=
3
;
...
...
@@ -157,6 +158,18 @@ import java.util.Locale;
}
Log
.
d
(
TAG
,
" ]"
);
}
// Log metadata for at most one of the tracks selected for the renderer.
if
(
trackSelection
!=
null
)
{
for
(
int
selectionIndex
=
0
;
selectionIndex
<
trackSelection
.
length
();
selectionIndex
++)
{
Metadata
metadata
=
trackSelection
.
getFormat
(
selectionIndex
).
metadata
;
if
(
metadata
!=
null
)
{
Log
.
d
(
TAG
,
" Metadata ["
);
printMetadata
(
metadata
,
" "
);
Log
.
d
(
TAG
,
" ]"
);
break
;
}
}
}
Log
.
d
(
TAG
,
" ]"
);
}
}
...
...
@@ -182,34 +195,13 @@ import java.util.Locale;
Log
.
d
(
TAG
,
"]"
);
}
// MetadataRenderer.Output
<List<Id3Frame>>
// MetadataRenderer.Output
@Override
public
void
onMetadata
(
List
<
Id3Frame
>
id3Frames
)
{
for
(
Id3Frame
id3Frame
:
id3Frames
)
{
if
(
id3Frame
instanceof
TxxxFrame
)
{
TxxxFrame
txxxFrame
=
(
TxxxFrame
)
id3Frame
;
Log
.
i
(
TAG
,
String
.
format
(
"ID3 TimedMetadata %s: description=%s, value=%s"
,
txxxFrame
.
id
,
txxxFrame
.
description
,
txxxFrame
.
value
));
}
else
if
(
id3Frame
instanceof
PrivFrame
)
{
PrivFrame
privFrame
=
(
PrivFrame
)
id3Frame
;
Log
.
i
(
TAG
,
String
.
format
(
"ID3 TimedMetadata %s: owner=%s"
,
privFrame
.
id
,
privFrame
.
owner
));
}
else
if
(
id3Frame
instanceof
GeobFrame
)
{
GeobFrame
geobFrame
=
(
GeobFrame
)
id3Frame
;
Log
.
i
(
TAG
,
String
.
format
(
"ID3 TimedMetadata %s: mimeType=%s, filename=%s, description=%s"
,
geobFrame
.
id
,
geobFrame
.
mimeType
,
geobFrame
.
filename
,
geobFrame
.
description
));
}
else
if
(
id3Frame
instanceof
ApicFrame
)
{
ApicFrame
apicFrame
=
(
ApicFrame
)
id3Frame
;
Log
.
i
(
TAG
,
String
.
format
(
"ID3 TimedMetadata %s: mimeType=%s, description=%s"
,
apicFrame
.
id
,
apicFrame
.
mimeType
,
apicFrame
.
description
));
}
else
if
(
id3Frame
instanceof
TextInformationFrame
)
{
TextInformationFrame
textInformationFrame
=
(
TextInformationFrame
)
id3Frame
;
Log
.
i
(
TAG
,
String
.
format
(
"ID3 TimedMetadata %s: description=%s"
,
textInformationFrame
.
id
,
textInformationFrame
.
description
));
}
else
{
Log
.
i
(
TAG
,
String
.
format
(
"ID3 TimedMetadata %s"
,
id3Frame
.
id
));
}
}
public
void
onMetadata
(
Metadata
metadata
)
{
Log
.
d
(
TAG
,
"onMetadata ["
);
printMetadata
(
metadata
,
" "
);
Log
.
d
(
TAG
,
"]"
);
}
// AudioRendererEventListener
...
...
@@ -354,6 +346,39 @@ import java.util.Locale;
Log
.
e
(
TAG
,
"internalError ["
+
getSessionTimeString
()
+
", "
+
type
+
"]"
,
e
);
}
private
void
printMetadata
(
Metadata
metadata
,
String
prefix
)
{
for
(
int
i
=
0
;
i
<
metadata
.
length
();
i
++)
{
Metadata
.
Entry
entry
=
metadata
.
get
(
i
);
if
(
entry
instanceof
TxxxFrame
)
{
TxxxFrame
txxxFrame
=
(
TxxxFrame
)
entry
;
Log
.
d
(
TAG
,
prefix
+
String
.
format
(
"%s: description=%s, value=%s"
,
txxxFrame
.
id
,
txxxFrame
.
description
,
txxxFrame
.
value
));
}
else
if
(
entry
instanceof
PrivFrame
)
{
PrivFrame
privFrame
=
(
PrivFrame
)
entry
;
Log
.
d
(
TAG
,
prefix
+
String
.
format
(
"%s: owner=%s"
,
privFrame
.
id
,
privFrame
.
owner
));
}
else
if
(
entry
instanceof
GeobFrame
)
{
GeobFrame
geobFrame
=
(
GeobFrame
)
entry
;
Log
.
d
(
TAG
,
prefix
+
String
.
format
(
"%s: mimeType=%s, filename=%s, description=%s"
,
geobFrame
.
id
,
geobFrame
.
mimeType
,
geobFrame
.
filename
,
geobFrame
.
description
));
}
else
if
(
entry
instanceof
ApicFrame
)
{
ApicFrame
apicFrame
=
(
ApicFrame
)
entry
;
Log
.
d
(
TAG
,
prefix
+
String
.
format
(
"%s: mimeType=%s, description=%s"
,
apicFrame
.
id
,
apicFrame
.
mimeType
,
apicFrame
.
description
));
}
else
if
(
entry
instanceof
TextInformationFrame
)
{
TextInformationFrame
textInformationFrame
=
(
TextInformationFrame
)
entry
;
Log
.
d
(
TAG
,
prefix
+
String
.
format
(
"%s: description=%s"
,
textInformationFrame
.
id
,
textInformationFrame
.
description
));
}
else
if
(
entry
instanceof
CommentFrame
)
{
CommentFrame
commentFrame
=
(
CommentFrame
)
entry
;
Log
.
d
(
TAG
,
prefix
+
String
.
format
(
"%s: language=%s description=%s"
,
commentFrame
.
id
,
commentFrame
.
language
,
commentFrame
.
description
,
commentFrame
.
text
));
}
else
if
(
entry
instanceof
Id3Frame
)
{
Id3Frame
id3Frame
=
(
Id3Frame
)
entry
;
Log
.
d
(
TAG
,
prefix
+
String
.
format
(
"%s"
,
id3Frame
.
id
));
}
}
}
private
String
getSessionTimeString
()
{
return
getTimeString
(
SystemClock
.
elapsedRealtime
()
-
startTimeMs
);
}
...
...
library/src/androidTest/java/com/google/android/exoplayer2/FormatTest.java
View file @
ced03e9a
...
...
@@ -24,6 +24,8 @@ import android.annotation.TargetApi;
import
android.media.MediaFormat
;
import
android.os.Parcel
;
import
com.google.android.exoplayer2.drm.DrmInitData
;
import
com.google.android.exoplayer2.metadata.Metadata
;
import
com.google.android.exoplayer2.metadata.id3.TextInformationFrame
;
import
com.google.android.exoplayer2.testutil.TestUtil
;
import
com.google.android.exoplayer2.util.MimeTypes
;
import
com.google.android.exoplayer2.util.Util
;
...
...
@@ -56,11 +58,14 @@ public final class FormatTest extends TestCase {
TestUtil
.
buildTestData
(
128
,
1
/* data seed */
));
DrmInitData
drmInitData
=
new
DrmInitData
(
DRM_DATA_1
,
DRM_DATA_2
);
byte
[]
projectionData
=
new
byte
[]
{
1
,
2
,
3
};
Metadata
metadata
=
new
Metadata
(
new
TextInformationFrame
(
"id1"
,
"description1"
),
new
TextInformationFrame
(
"id2"
,
"description2"
));
Format
formatToParcel
=
new
Format
(
"id"
,
MimeTypes
.
VIDEO_MP4
,
MimeTypes
.
VIDEO_H264
,
null
,
1024
,
2048
,
1920
,
1080
,
24
,
90
,
2
,
projectionData
,
C
.
STEREO_MODE_TOP_BOTTOM
,
6
,
44100
,
C
.
ENCODING_PCM_24BIT
,
1001
,
1002
,
0
,
"und"
,
Format
.
OFFSET_SAMPLE_RELATIVE
,
INIT_DATA
,
drmInitData
);
drmInitData
,
metadata
);
Parcel
parcel
=
Parcel
.
obtain
();
formatToParcel
.
writeToParcel
(
parcel
,
0
);
...
...
library/src/androidTest/java/com/google/android/exoplayer2/metadata/id3/Id3DecoderTest.java
View file @
ced03e9a
...
...
@@ -16,8 +16,8 @@
package
com
.
google
.
android
.
exoplayer2
.
metadata
.
id3
;
import
android.test.MoreAsserts
;
import
com.google.android.exoplayer2.metadata.Metadata
;
import
com.google.android.exoplayer2.metadata.MetadataDecoderException
;
import
java.util.List
;
import
junit.framework.TestCase
;
/**
...
...
@@ -30,9 +30,9 @@ public class Id3DecoderTest extends TestCase {
3
,
0
,
109
,
100
,
105
,
97
,
108
,
111
,
103
,
95
,
86
,
73
,
78
,
68
,
73
,
67
,
79
,
49
,
53
,
50
,
55
,
54
,
54
,
52
,
95
,
115
,
116
,
97
,
114
,
116
,
0
};
Id3Decoder
decoder
=
new
Id3Decoder
();
List
<
Id3Frame
>
id3Frames
=
decoder
.
decode
(
rawId3
,
rawId3
.
length
);
assertEquals
(
1
,
id3Frames
.
size
());
TxxxFrame
txxxFrame
=
(
TxxxFrame
)
id3Frames
.
get
(
0
);
Metadata
metadata
=
decoder
.
decode
(
rawId3
,
rawId3
.
length
);
assertEquals
(
1
,
metadata
.
length
());
TxxxFrame
txxxFrame
=
(
TxxxFrame
)
metadata
.
get
(
0
);
assertEquals
(
""
,
txxxFrame
.
description
);
assertEquals
(
"mdialog_VINDICO1527664_start"
,
txxxFrame
.
value
);
}
...
...
@@ -42,9 +42,9 @@ public class Id3DecoderTest extends TestCase {
3
,
105
,
109
,
97
,
103
,
101
,
47
,
106
,
112
,
101
,
103
,
0
,
16
,
72
,
101
,
108
,
108
,
111
,
32
,
87
,
111
,
114
,
108
,
100
,
0
,
1
,
2
,
3
,
4
,
5
,
6
,
7
,
8
,
9
,
0
};
Id3Decoder
decoder
=
new
Id3Decoder
();
List
<
Id3Frame
>
id3Frames
=
decoder
.
decode
(
rawId3
,
rawId3
.
length
);
assertEquals
(
1
,
id3Frames
.
size
());
ApicFrame
apicFrame
=
(
ApicFrame
)
id3Frames
.
get
(
0
);
Metadata
metadata
=
decoder
.
decode
(
rawId3
,
rawId3
.
length
);
assertEquals
(
1
,
metadata
.
length
());
ApicFrame
apicFrame
=
(
ApicFrame
)
metadata
.
get
(
0
);
assertEquals
(
"image/jpeg"
,
apicFrame
.
mimeType
);
assertEquals
(
16
,
apicFrame
.
pictureType
);
assertEquals
(
"Hello World"
,
apicFrame
.
description
);
...
...
@@ -56,9 +56,9 @@ public class Id3DecoderTest extends TestCase {
byte
[]
rawId3
=
new
byte
[]
{
73
,
68
,
51
,
4
,
0
,
0
,
0
,
0
,
0
,
23
,
84
,
73
,
84
,
50
,
0
,
0
,
0
,
13
,
0
,
0
,
3
,
72
,
101
,
108
,
108
,
111
,
32
,
87
,
111
,
114
,
108
,
100
,
0
};
Id3Decoder
decoder
=
new
Id3Decoder
();
List
<
Id3Frame
>
id3Frames
=
decoder
.
decode
(
rawId3
,
rawId3
.
length
);
assertEquals
(
1
,
id3Frames
.
size
());
TextInformationFrame
textInformationFrame
=
(
TextInformationFrame
)
id3Frames
.
get
(
0
);
Metadata
metadata
=
decoder
.
decode
(
rawId3
,
rawId3
.
length
);
assertEquals
(
1
,
metadata
.
length
());
TextInformationFrame
textInformationFrame
=
(
TextInformationFrame
)
metadata
.
get
(
0
);
assertEquals
(
"TIT2"
,
textInformationFrame
.
id
);
assertEquals
(
"Hello World"
,
textInformationFrame
.
description
);
}
...
...
library/src/main/java/com/google/android/exoplayer2/Format.java
View file @
ced03e9a
...
...
@@ -21,6 +21,7 @@ import android.media.MediaFormat;
import
android.os.Parcel
;
import
android.os.Parcelable
;
import
com.google.android.exoplayer2.drm.DrmInitData
;
import
com.google.android.exoplayer2.metadata.Metadata
;
import
com.google.android.exoplayer2.util.MimeTypes
;
import
com.google.android.exoplayer2.util.Util
;
import
java.nio.ByteBuffer
;
...
...
@@ -57,6 +58,10 @@ public final class Format implements Parcelable {
* Codecs of the format as described in RFC 6381, or null if unknown or not applicable.
*/
public
final
String
codecs
;
/**
* Metadata, or null if unknown or not applicable.
*/
public
final
Metadata
metadata
;
// Container specific.
...
...
@@ -185,7 +190,7 @@ public final class Format implements Parcelable {
float
frameRate
,
List
<
byte
[]>
initializationData
)
{
return
new
Format
(
id
,
containerMimeType
,
sampleMimeType
,
codecs
,
bitrate
,
NO_VALUE
,
width
,
height
,
frameRate
,
NO_VALUE
,
NO_VALUE
,
null
,
NO_VALUE
,
NO_VALUE
,
NO_VALUE
,
NO_VALUE
,
NO_VALUE
,
NO_VALUE
,
0
,
null
,
OFFSET_SAMPLE_RELATIVE
,
initializationData
,
null
);
NO_VALUE
,
NO_VALUE
,
0
,
null
,
OFFSET_SAMPLE_RELATIVE
,
initializationData
,
null
,
null
);
}
public
static
Format
createVideoSampleFormat
(
String
id
,
String
sampleMimeType
,
String
codecs
,
...
...
@@ -211,7 +216,7 @@ public final class Format implements Parcelable {
return
new
Format
(
id
,
null
,
sampleMimeType
,
codecs
,
bitrate
,
maxInputSize
,
width
,
height
,
frameRate
,
rotationDegrees
,
pixelWidthHeightRatio
,
projectionData
,
stereoMode
,
NO_VALUE
,
NO_VALUE
,
NO_VALUE
,
NO_VALUE
,
NO_VALUE
,
0
,
null
,
OFFSET_SAMPLE_RELATIVE
,
initializationData
,
drmInitData
);
drmInitData
,
null
);
}
// Audio.
...
...
@@ -222,7 +227,7 @@ public final class Format implements Parcelable {
return
new
Format
(
id
,
containerMimeType
,
sampleMimeType
,
codecs
,
bitrate
,
NO_VALUE
,
NO_VALUE
,
NO_VALUE
,
NO_VALUE
,
NO_VALUE
,
NO_VALUE
,
null
,
NO_VALUE
,
channelCount
,
sampleRate
,
NO_VALUE
,
NO_VALUE
,
NO_VALUE
,
selectionFlags
,
language
,
OFFSET_SAMPLE_RELATIVE
,
initializationData
,
null
);
null
,
null
);
}
public
static
Format
createAudioSampleFormat
(
String
id
,
String
sampleMimeType
,
String
codecs
,
...
...
@@ -239,18 +244,18 @@ public final class Format implements Parcelable {
@C
.
SelectionFlags
int
selectionFlags
,
String
language
)
{
return
createAudioSampleFormat
(
id
,
sampleMimeType
,
codecs
,
bitrate
,
maxInputSize
,
channelCount
,
sampleRate
,
pcmEncoding
,
NO_VALUE
,
NO_VALUE
,
initializationData
,
drmInitData
,
selectionFlags
,
language
);
selectionFlags
,
language
,
null
);
}
public
static
Format
createAudioSampleFormat
(
String
id
,
String
sampleMimeType
,
String
codecs
,
int
bitrate
,
int
maxInputSize
,
int
channelCount
,
int
sampleRate
,
@C
.
PcmEncoding
int
pcmEncoding
,
int
encoderDelay
,
int
encoderPadding
,
List
<
byte
[]>
initializationData
,
DrmInitData
drmInitData
,
@C
.
SelectionFlags
int
selectionFlags
,
String
language
)
{
@C
.
SelectionFlags
int
selectionFlags
,
String
language
,
Metadata
metadata
)
{
return
new
Format
(
id
,
null
,
sampleMimeType
,
codecs
,
bitrate
,
maxInputSize
,
NO_VALUE
,
NO_VALUE
,
NO_VALUE
,
NO_VALUE
,
NO_VALUE
,
null
,
NO_VALUE
,
channelCount
,
sampleRate
,
pcmEncoding
,
encoderDelay
,
encoderPadding
,
selectionFlags
,
language
,
OFFSET_SAMPLE_RELATIVE
,
initializationData
,
drmInitData
);
initializationData
,
drmInitData
,
metadata
);
}
// Text.
...
...
@@ -260,7 +265,7 @@ public final class Format implements Parcelable {
String
language
)
{
return
new
Format
(
id
,
containerMimeType
,
sampleMimeType
,
codecs
,
bitrate
,
NO_VALUE
,
NO_VALUE
,
NO_VALUE
,
NO_VALUE
,
NO_VALUE
,
NO_VALUE
,
null
,
NO_VALUE
,
NO_VALUE
,
NO_VALUE
,
NO_VALUE
,
NO_VALUE
,
NO_VALUE
,
selectionFlags
,
language
,
OFFSET_SAMPLE_RELATIVE
,
null
,
null
);
NO_VALUE
,
NO_VALUE
,
selectionFlags
,
language
,
OFFSET_SAMPLE_RELATIVE
,
null
,
null
,
null
);
}
public
static
Format
createTextSampleFormat
(
String
id
,
String
sampleMimeType
,
String
codecs
,
...
...
@@ -274,7 +279,7 @@ public final class Format implements Parcelable {
long
subsampleOffsetUs
)
{
return
new
Format
(
id
,
null
,
sampleMimeType
,
codecs
,
bitrate
,
NO_VALUE
,
NO_VALUE
,
NO_VALUE
,
NO_VALUE
,
NO_VALUE
,
NO_VALUE
,
null
,
NO_VALUE
,
NO_VALUE
,
NO_VALUE
,
NO_VALUE
,
NO_VALUE
,
NO_VALUE
,
selectionFlags
,
language
,
subsampleOffsetUs
,
null
,
drmInitData
);
NO_VALUE
,
selectionFlags
,
language
,
subsampleOffsetUs
,
null
,
drmInitData
,
null
);
}
// Image.
...
...
@@ -283,7 +288,7 @@ public final class Format implements Parcelable {
int
bitrate
,
List
<
byte
[]>
initializationData
,
String
language
,
DrmInitData
drmInitData
)
{
return
new
Format
(
id
,
null
,
sampleMimeType
,
codecs
,
bitrate
,
NO_VALUE
,
NO_VALUE
,
NO_VALUE
,
NO_VALUE
,
NO_VALUE
,
NO_VALUE
,
null
,
NO_VALUE
,
NO_VALUE
,
NO_VALUE
,
NO_VALUE
,
NO_VALUE
,
NO_VALUE
,
0
,
language
,
OFFSET_SAMPLE_RELATIVE
,
initializationData
,
drmInitData
);
NO_VALUE
,
0
,
language
,
OFFSET_SAMPLE_RELATIVE
,
initializationData
,
drmInitData
,
null
);
}
// Generic.
...
...
@@ -292,14 +297,14 @@ public final class Format implements Parcelable {
String
sampleMimeType
,
int
bitrate
)
{
return
new
Format
(
id
,
containerMimeType
,
sampleMimeType
,
codecs
,
bitrate
,
NO_VALUE
,
NO_VALUE
,
NO_VALUE
,
NO_VALUE
,
NO_VALUE
,
NO_VALUE
,
null
,
NO_VALUE
,
NO_VALUE
,
NO_VALUE
,
NO_VALUE
,
NO_VALUE
,
NO_VALUE
,
0
,
null
,
OFFSET_SAMPLE_RELATIVE
,
null
,
null
);
NO_VALUE
,
NO_VALUE
,
0
,
null
,
OFFSET_SAMPLE_RELATIVE
,
null
,
null
,
null
);
}
public
static
Format
createSampleFormat
(
String
id
,
String
sampleMimeType
,
String
codecs
,
int
bitrate
,
DrmInitData
drmInitData
)
{
return
new
Format
(
id
,
null
,
sampleMimeType
,
codecs
,
bitrate
,
NO_VALUE
,
NO_VALUE
,
NO_VALUE
,
NO_VALUE
,
NO_VALUE
,
NO_VALUE
,
null
,
NO_VALUE
,
NO_VALUE
,
NO_VALUE
,
NO_VALUE
,
NO_VALUE
,
NO_VALUE
,
0
,
null
,
OFFSET_SAMPLE_RELATIVE
,
null
,
drmInitData
);
NO_VALUE
,
0
,
null
,
OFFSET_SAMPLE_RELATIVE
,
null
,
drmInitData
,
null
);
}
/* package */
Format
(
String
id
,
String
containerMimeType
,
String
sampleMimeType
,
String
codecs
,
...
...
@@ -307,7 +312,8 @@ public final class Format implements Parcelable {
float
pixelWidthHeightRatio
,
byte
[]
projectionData
,
@C
.
StereoMode
int
stereoMode
,
int
channelCount
,
int
sampleRate
,
@C
.
PcmEncoding
int
pcmEncoding
,
int
encoderDelay
,
int
encoderPadding
,
@C
.
SelectionFlags
int
selectionFlags
,
String
language
,
long
subsampleOffsetUs
,
List
<
byte
[]>
initializationData
,
DrmInitData
drmInitData
)
{
long
subsampleOffsetUs
,
List
<
byte
[]>
initializationData
,
DrmInitData
drmInitData
,
Metadata
metadata
)
{
this
.
id
=
id
;
this
.
containerMimeType
=
containerMimeType
;
this
.
sampleMimeType
=
sampleMimeType
;
...
...
@@ -332,6 +338,7 @@ public final class Format implements Parcelable {
this
.
initializationData
=
initializationData
==
null
?
Collections
.<
byte
[]>
emptyList
()
:
initializationData
;
this
.
drmInitData
=
drmInitData
;
this
.
metadata
=
metadata
;
}
@SuppressWarnings
(
"ResourceType"
)
...
...
@@ -364,20 +371,21 @@ public final class Format implements Parcelable {
initializationData
.
add
(
in
.
createByteArray
());
}
drmInitData
=
in
.
readParcelable
(
DrmInitData
.
class
.
getClassLoader
());
metadata
=
in
.
readParcelable
(
Metadata
.
class
.
getClassLoader
());
}
public
Format
copyWithMaxInputSize
(
int
maxInputSize
)
{
return
new
Format
(
id
,
containerMimeType
,
sampleMimeType
,
codecs
,
bitrate
,
maxInputSize
,
width
,
height
,
frameRate
,
rotationDegrees
,
pixelWidthHeightRatio
,
projectionData
,
stereoMode
,
channelCount
,
sampleRate
,
pcmEncoding
,
encoderDelay
,
encoderPadding
,
selectionFlags
,
language
,
subsampleOffsetUs
,
initializationData
,
drmInitData
);
selectionFlags
,
language
,
subsampleOffsetUs
,
initializationData
,
drmInitData
,
metadata
);
}
public
Format
copyWithSubsampleOffsetUs
(
long
subsampleOffsetUs
)
{
return
new
Format
(
id
,
containerMimeType
,
sampleMimeType
,
codecs
,
bitrate
,
maxInputSize
,
width
,
height
,
frameRate
,
rotationDegrees
,
pixelWidthHeightRatio
,
projectionData
,
stereoMode
,
channelCount
,
sampleRate
,
pcmEncoding
,
encoderDelay
,
encoderPadding
,
selectionFlags
,
language
,
subsampleOffsetUs
,
initializationData
,
drmInitData
);
selectionFlags
,
language
,
subsampleOffsetUs
,
initializationData
,
drmInitData
,
metadata
);
}
public
Format
copyWithContainerInfo
(
String
id
,
String
codecs
,
int
bitrate
,
int
width
,
int
height
,
...
...
@@ -385,7 +393,7 @@ public final class Format implements Parcelable {
return
new
Format
(
id
,
containerMimeType
,
sampleMimeType
,
codecs
,
bitrate
,
maxInputSize
,
width
,
height
,
frameRate
,
rotationDegrees
,
pixelWidthHeightRatio
,
projectionData
,
stereoMode
,
channelCount
,
sampleRate
,
pcmEncoding
,
encoderDelay
,
encoderPadding
,
selectionFlags
,
language
,
subsampleOffsetUs
,
initializationData
,
drmInitData
);
selectionFlags
,
language
,
subsampleOffsetUs
,
initializationData
,
drmInitData
,
metadata
);
}
public
Format
copyWithManifestFormatInfo
(
Format
manifestFormat
,
...
...
@@ -401,21 +409,28 @@ public final class Format implements Parcelable {
return
new
Format
(
id
,
containerMimeType
,
sampleMimeType
,
codecs
,
bitrate
,
maxInputSize
,
width
,
height
,
frameRate
,
rotationDegrees
,
pixelWidthHeightRatio
,
projectionData
,
stereoMode
,
channelCount
,
sampleRate
,
pcmEncoding
,
encoderDelay
,
encoderPadding
,
selectionFlags
,
language
,
subsampleOffsetUs
,
initializationData
,
drmInitData
);
language
,
subsampleOffsetUs
,
initializationData
,
drmInitData
,
metadata
);
}
public
Format
copyWithGaplessInfo
(
int
encoderDelay
,
int
encoderPadding
)
{
return
new
Format
(
id
,
containerMimeType
,
sampleMimeType
,
codecs
,
bitrate
,
maxInputSize
,
width
,
height
,
frameRate
,
rotationDegrees
,
pixelWidthHeightRatio
,
projectionData
,
stereoMode
,
channelCount
,
sampleRate
,
pcmEncoding
,
encoderDelay
,
encoderPadding
,
selectionFlags
,
language
,
subsampleOffsetUs
,
initializationData
,
drmInitData
);
selectionFlags
,
language
,
subsampleOffsetUs
,
initializationData
,
drmInitData
,
metadata
);
}
public
Format
copyWithDrmInitData
(
DrmInitData
drmInitData
)
{
return
new
Format
(
id
,
containerMimeType
,
sampleMimeType
,
codecs
,
bitrate
,
maxInputSize
,
width
,
height
,
frameRate
,
rotationDegrees
,
pixelWidthHeightRatio
,
projectionData
,
stereoMode
,
channelCount
,
sampleRate
,
pcmEncoding
,
encoderDelay
,
encoderPadding
,
selectionFlags
,
language
,
subsampleOffsetUs
,
initializationData
,
drmInitData
);
selectionFlags
,
language
,
subsampleOffsetUs
,
initializationData
,
drmInitData
,
metadata
);
}
public
Format
copyWithMetadata
(
Metadata
metadata
)
{
return
new
Format
(
id
,
containerMimeType
,
sampleMimeType
,
codecs
,
bitrate
,
maxInputSize
,
width
,
height
,
frameRate
,
rotationDegrees
,
pixelWidthHeightRatio
,
projectionData
,
stereoMode
,
channelCount
,
sampleRate
,
pcmEncoding
,
encoderDelay
,
encoderPadding
,
selectionFlags
,
language
,
subsampleOffsetUs
,
initializationData
,
drmInitData
,
metadata
);
}
/**
...
...
@@ -475,6 +490,7 @@ public final class Format implements Parcelable {
result
=
31
*
result
+
sampleRate
;
result
=
31
*
result
+
(
language
==
null
?
0
:
language
.
hashCode
());
result
=
31
*
result
+
(
drmInitData
==
null
?
0
:
drmInitData
.
hashCode
());
result
=
31
*
result
+
(
metadata
==
null
?
0
:
metadata
.
hashCode
());
hashCode
=
result
;
}
return
hashCode
;
...
...
@@ -502,6 +518,7 @@ public final class Format implements Parcelable {
||
!
Util
.
areEqual
(
sampleMimeType
,
other
.
sampleMimeType
)
||
!
Util
.
areEqual
(
codecs
,
other
.
codecs
)
||
!
Util
.
areEqual
(
drmInitData
,
other
.
drmInitData
)
||
!
Util
.
areEqual
(
metadata
,
other
.
metadata
)
||
!
Arrays
.
equals
(
projectionData
,
other
.
projectionData
)
||
initializationData
.
size
()
!=
other
.
initializationData
.
size
())
{
return
false
;
...
...
@@ -574,6 +591,7 @@ public final class Format implements Parcelable {
dest
.
writeByteArray
(
initializationData
.
get
(
i
));
}
dest
.
writeParcelable
(
drmInitData
,
0
);
dest
.
writeParcelable
(
metadata
,
0
);
}
/**
...
...
library/src/main/java/com/google/android/exoplayer2/SimpleExoPlayer.java
View file @
ced03e9a
...
...
@@ -35,9 +35,9 @@ import com.google.android.exoplayer2.decoder.DecoderCounters;
import
com.google.android.exoplayer2.drm.DrmSessionManager
;
import
com.google.android.exoplayer2.drm.FrameworkMediaCrypto
;
import
com.google.android.exoplayer2.mediacodec.MediaCodecSelector
;
import
com.google.android.exoplayer2.metadata.Metadata
;
import
com.google.android.exoplayer2.metadata.MetadataRenderer
;
import
com.google.android.exoplayer2.metadata.id3.Id3Decoder
;
import
com.google.android.exoplayer2.metadata.id3.Id3Frame
;
import
com.google.android.exoplayer2.source.MediaSource
;
import
com.google.android.exoplayer2.source.TrackGroupArray
;
import
com.google.android.exoplayer2.text.Cue
;
...
...
@@ -107,7 +107,7 @@ public final class SimpleExoPlayer implements ExoPlayer {
private
SurfaceHolder
surfaceHolder
;
private
TextureView
textureView
;
private
TextRenderer
.
Output
textOutput
;
private
MetadataRenderer
.
Output
<
List
<
Id3Frame
>>
id3
Output
;
private
MetadataRenderer
.
Output
metadata
Output
;
private
VideoListener
videoListener
;
private
AudioRendererEventListener
audioDebugListener
;
private
VideoRendererEventListener
videoDebugListener
;
...
...
@@ -364,12 +364,21 @@ public final class SimpleExoPlayer implements ExoPlayer {
}
/**
* Sets a listener to receive ID3 metadata events.
* @deprecated Use {@link #setMetadataOutput(MetadataRenderer.Output)} instead.
* @param output The output.
*/
@Deprecated
public
void
setId3Output
(
MetadataRenderer
.
Output
output
)
{
setMetadataOutput
(
output
);
}
/**
* Sets a listener to receive metadata events.
*
* @param output The output.
*/
public
void
set
Id3Output
(
MetadataRenderer
.
Output
<
List
<
Id3Frame
>>
output
)
{
id3
Output
=
output
;
public
void
set
MetadataOutput
(
MetadataRenderer
.
Output
output
)
{
metadata
Output
=
output
;
}
// ExoPlayer implementation
...
...
@@ -540,9 +549,9 @@ public final class SimpleExoPlayer implements ExoPlayer {
Renderer
textRenderer
=
new
TextRenderer
(
componentListener
,
mainHandler
.
getLooper
());
renderersList
.
add
(
textRenderer
);
MetadataRenderer
<
List
<
Id3Frame
>>
id3Renderer
=
new
MetadataRenderer
<>
(
componentListener
,
MetadataRenderer
metadataRenderer
=
new
MetadataRenderer
(
componentListener
,
mainHandler
.
getLooper
(),
new
Id3Decoder
());
renderersList
.
add
(
id3
Renderer
);
renderersList
.
add
(
metadata
Renderer
);
}
private
void
buildExtensionRenderers
(
ArrayList
<
Renderer
>
renderersList
,
...
...
@@ -644,7 +653,7 @@ public final class SimpleExoPlayer implements ExoPlayer {
}
private
final
class
ComponentListener
implements
VideoRendererEventListener
,
AudioRendererEventListener
,
TextRenderer
.
Output
,
MetadataRenderer
.
Output
<
List
<
Id3Frame
>>
,
AudioRendererEventListener
,
TextRenderer
.
Output
,
MetadataRenderer
.
Output
,
SurfaceHolder
.
Callback
,
TextureView
.
SurfaceTextureListener
{
// VideoRendererEventListener implementation
...
...
@@ -775,12 +784,12 @@ public final class SimpleExoPlayer implements ExoPlayer {
}
}
// MetadataRenderer.Output
<List<Id3Frame>>
implementation
// MetadataRenderer.Output implementation
@Override
public
void
onMetadata
(
List
<
Id3Frame
>
id3Frames
)
{
if
(
id3
Output
!=
null
)
{
id3Output
.
onMetadata
(
id3Frames
);
public
void
onMetadata
(
Metadata
metadata
)
{
if
(
metadata
Output
!=
null
)
{
metadataOutput
.
onMetadata
(
metadata
);
}
}
...
...
library/src/main/java/com/google/android/exoplayer2/extractor/GaplessInfoHolder.java
View file @
ced03e9a
...
...
@@ -16,6 +16,8 @@
package
com
.
google
.
android
.
exoplayer2
.
extractor
;
import
com.google.android.exoplayer2.Format
;
import
com.google.android.exoplayer2.metadata.Metadata
;
import
com.google.android.exoplayer2.metadata.id3.CommentFrame
;
import
java.util.regex.Matcher
;
import
java.util.regex.Pattern
;
...
...
@@ -66,6 +68,25 @@ public final class GaplessInfoHolder {
}
/**
* Populates the holder with data parsed from ID3 {@link Metadata}.
*
* @param metadata The metadata from which to parse the gapless information.
* @return Whether the holder was populated.
*/
public
boolean
setFromMetadata
(
Metadata
metadata
)
{
for
(
int
i
=
0
;
i
<
metadata
.
length
();
i
++)
{
Metadata
.
Entry
entry
=
metadata
.
get
(
i
);
if
(
entry
instanceof
CommentFrame
)
{
CommentFrame
commentFrame
=
(
CommentFrame
)
entry
;
if
(
setFromComment
(
commentFrame
.
description
,
commentFrame
.
text
))
{
return
true
;
}
}
}
return
false
;
}
/**
* Populates the holder with data parsed from a gapless playback comment (stored in an ID3 header
* or MPEG 4 user data), if valid and non-zero.
*
...
...
@@ -73,7 +94,7 @@ public final class GaplessInfoHolder {
* @param data The comment's payload data.
* @return Whether the holder was populated.
*/
p
ublic
boolean
setFromComment
(
String
name
,
String
data
)
{
p
rivate
boolean
setFromComment
(
String
name
,
String
data
)
{
if
(!
GAPLESS_COMMENT_ID
.
equals
(
name
))
{
return
false
;
}
...
...
library/src/main/java/com/google/android/exoplayer2/extractor/mp3/Id3Util.java
deleted
100644 → 0
View file @
2c543632
/*
* Copyright (C) 2016 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
.
exoplayer2
.
extractor
.
mp3
;
import
android.util.Pair
;
import
com.google.android.exoplayer2.extractor.ExtractorInput
;
import
com.google.android.exoplayer2.extractor.GaplessInfoHolder
;
import
com.google.android.exoplayer2.util.ParsableByteArray
;
import
com.google.android.exoplayer2.util.Util
;
import
java.io.IOException
;
import
java.nio.charset.Charset
;
/**
* Utility for parsing ID3 version 2 metadata in MP3 files.
*/
/* package */
final
class
Id3Util
{
/**
* The maximum valid length for metadata in bytes.
*/
private
static
final
int
MAXIMUM_METADATA_SIZE
=
3
*
1024
*
1024
;
private
static
final
int
ID3_TAG
=
Util
.
getIntegerCodeForString
(
"ID3"
);
private
static
final
Charset
[]
CHARSET_BY_ENCODING
=
new
Charset
[]
{
Charset
.
forName
(
"ISO-8859-1"
),
Charset
.
forName
(
"UTF-16LE"
),
Charset
.
forName
(
"UTF-16BE"
),
Charset
.
forName
(
"UTF-8"
)};
/**
* Peeks data from the input and parses ID3 metadata.
*
* @param input The {@link ExtractorInput} from which data should be peeked.
* @param out The {@link GaplessInfoHolder} to populate.
* @throws IOException If an error occurred peeking from the input.
* @throws InterruptedException If the thread was interrupted.
*/
public
static
void
parseId3
(
ExtractorInput
input
,
GaplessInfoHolder
out
)
throws
IOException
,
InterruptedException
{
ParsableByteArray
scratch
=
new
ParsableByteArray
(
10
);
int
peekedId3Bytes
=
0
;
while
(
true
)
{
input
.
peekFully
(
scratch
.
data
,
0
,
10
);
scratch
.
setPosition
(
0
);
if
(
scratch
.
readUnsignedInt24
()
!=
ID3_TAG
)
{
break
;
}
int
majorVersion
=
scratch
.
readUnsignedByte
();
int
minorVersion
=
scratch
.
readUnsignedByte
();
int
flags
=
scratch
.
readUnsignedByte
();
int
length
=
scratch
.
readSynchSafeInt
();
if
(!
out
.
hasGaplessInfo
()
&&
canParseMetadata
(
majorVersion
,
minorVersion
,
flags
,
length
))
{
byte
[]
frame
=
new
byte
[
length
];
input
.
peekFully
(
frame
,
0
,
length
);
parseGaplessInfo
(
new
ParsableByteArray
(
frame
),
majorVersion
,
flags
,
out
);
}
else
{
input
.
advancePeekPosition
(
length
);
}
peekedId3Bytes
+=
10
+
length
;
}
input
.
resetPeekPosition
();
input
.
advancePeekPosition
(
peekedId3Bytes
);
}
private
static
boolean
canParseMetadata
(
int
majorVersion
,
int
minorVersion
,
int
flags
,
int
length
)
{
return
minorVersion
!=
0xFF
&&
majorVersion
>=
2
&&
majorVersion
<=
4
&&
length
<=
MAXIMUM_METADATA_SIZE
&&
!(
majorVersion
==
2
&&
((
flags
&
0x3F
)
!=
0
||
(
flags
&
0x40
)
!=
0
))
&&
!(
majorVersion
==
3
&&
(
flags
&
0x1F
)
!=
0
)
&&
!(
majorVersion
==
4
&&
(
flags
&
0x0F
)
!=
0
);
}
private
static
void
parseGaplessInfo
(
ParsableByteArray
frame
,
int
version
,
int
flags
,
GaplessInfoHolder
out
)
{
unescape
(
frame
,
version
,
flags
);
// Skip any extended header.
frame
.
setPosition
(
0
);
if
(
version
==
3
&&
(
flags
&
0x40
)
!=
0
)
{
if
(
frame
.
bytesLeft
()
<
4
)
{
return
;
}
int
extendedHeaderSize
=
frame
.
readUnsignedIntToInt
();
if
(
extendedHeaderSize
>
frame
.
bytesLeft
())
{
return
;
}
int
paddingSize
;
if
(
extendedHeaderSize
>=
6
)
{
frame
.
skipBytes
(
2
);
// extended flags
paddingSize
=
frame
.
readUnsignedIntToInt
();
frame
.
setPosition
(
4
);
frame
.
setLimit
(
frame
.
limit
()
-
paddingSize
);
if
(
frame
.
bytesLeft
()
<
extendedHeaderSize
)
{
return
;
}
}
frame
.
skipBytes
(
extendedHeaderSize
);
}
else
if
(
version
==
4
&&
(
flags
&
0x40
)
!=
0
)
{
if
(
frame
.
bytesLeft
()
<
4
)
{
return
;
}
int
extendedHeaderSize
=
frame
.
readSynchSafeInt
();
if
(
extendedHeaderSize
<
6
||
extendedHeaderSize
>
frame
.
bytesLeft
()
+
4
)
{
return
;
}
frame
.
setPosition
(
extendedHeaderSize
);
}
// Extract gapless playback metadata stored in comments.
Pair
<
String
,
String
>
comment
;
while
((
comment
=
findNextComment
(
version
,
frame
))
!=
null
)
{
if
(
comment
.
first
.
length
()
>
3
)
{
if
(
out
.
setFromComment
(
comment
.
first
.
substring
(
3
),
comment
.
second
))
{
break
;
}
}
}
}
private
static
Pair
<
String
,
String
>
findNextComment
(
int
majorVersion
,
ParsableByteArray
data
)
{
int
frameSize
;
while
(
true
)
{
if
(
majorVersion
==
2
)
{
if
(
data
.
bytesLeft
()
<
6
)
{
return
null
;
}
String
id
=
data
.
readString
(
3
,
Charset
.
forName
(
"US-ASCII"
));
if
(
id
.
equals
(
"\0\0\0"
))
{
return
null
;
}
frameSize
=
data
.
readUnsignedInt24
();
if
(
frameSize
==
0
||
frameSize
>
data
.
bytesLeft
())
{
return
null
;
}
if
(
id
.
equals
(
"COM"
))
{
break
;
}
}
else
/* major == 3 || major == 4 */
{
if
(
data
.
bytesLeft
()
<
10
)
{
return
null
;
}
String
id
=
data
.
readString
(
4
,
Charset
.
forName
(
"US-ASCII"
));
if
(
id
.
equals
(
"\0\0\0\0"
))
{
return
null
;
}
frameSize
=
majorVersion
==
4
?
data
.
readSynchSafeInt
()
:
data
.
readUnsignedIntToInt
();
if
(
frameSize
==
0
||
frameSize
>
data
.
bytesLeft
()
-
2
)
{
return
null
;
}
int
flags
=
data
.
readUnsignedShort
();
boolean
compressedOrEncrypted
=
(
majorVersion
==
4
&&
(
flags
&
0x0C
)
!=
0
)
||
(
majorVersion
==
3
&&
(
flags
&
0xC0
)
!=
0
);
if
(!
compressedOrEncrypted
&&
id
.
equals
(
"COMM"
))
{
break
;
}
}
data
.
skipBytes
(
frameSize
);
}
// The comment tag is at the reading position in data.
int
encoding
=
data
.
readUnsignedByte
();
if
(
encoding
<
0
||
encoding
>=
CHARSET_BY_ENCODING
.
length
)
{
return
null
;
}
Charset
charset
=
CHARSET_BY_ENCODING
[
encoding
];
String
[]
commentFields
=
data
.
readString
(
frameSize
-
1
,
charset
).
split
(
"\0"
);
return
commentFields
.
length
==
2
?
Pair
.
create
(
commentFields
[
0
],
commentFields
[
1
])
:
null
;
}
private
static
boolean
unescape
(
ParsableByteArray
frame
,
int
version
,
int
flags
)
{
if
(
version
!=
4
)
{
if
((
flags
&
0x80
)
!=
0
)
{
// Remove unsynchronization on ID3 version < 2.4.0.
byte
[]
bytes
=
frame
.
data
;
int
newLength
=
bytes
.
length
;
for
(
int
i
=
0
;
i
+
1
<
newLength
;
i
++)
{
if
((
bytes
[
i
]
&
0xFF
)
==
0xFF
&&
bytes
[
i
+
1
]
==
0x00
)
{
System
.
arraycopy
(
bytes
,
i
+
2
,
bytes
,
i
+
1
,
newLength
-
i
-
2
);
newLength
--;
}
}
frame
.
setLimit
(
newLength
);
}
}
else
{
// Remove unsynchronization on ID3 version 2.4.0.
if
(
canUnescapeVersion4
(
frame
,
false
))
{
unescapeVersion4
(
frame
,
false
);
}
else
if
(
canUnescapeVersion4
(
frame
,
true
))
{
unescapeVersion4
(
frame
,
true
);
}
else
{
return
false
;
}
}
return
true
;
}
private
static
boolean
canUnescapeVersion4
(
ParsableByteArray
frame
,
boolean
unsignedIntDataSizeHack
)
{
frame
.
setPosition
(
0
);
while
(
frame
.
bytesLeft
()
>=
10
)
{
if
(
frame
.
readInt
()
==
0
)
{
return
true
;
}
long
dataSize
=
frame
.
readUnsignedInt
();
if
(!
unsignedIntDataSizeHack
)
{
// Parse the data size as a syncsafe integer.
if
((
dataSize
&
0x808080
L
)
!=
0
)
{
return
false
;
}
dataSize
=
(
dataSize
&
0x7F
)
|
(((
dataSize
>>
8
)
&
0x7F
)
<<
7
)
|
(((
dataSize
>>
16
)
&
0x7F
)
<<
14
)
|
(((
dataSize
>>
24
)
&
0x7F
)
<<
21
);
}
if
(
dataSize
>
frame
.
bytesLeft
()
-
2
)
{
return
false
;
}
int
flags
=
frame
.
readUnsignedShort
();
if
((
flags
&
1
)
!=
0
)
{
if
(
frame
.
bytesLeft
()
<
4
)
{
return
false
;
}
}
frame
.
skipBytes
((
int
)
dataSize
);
}
return
true
;
}
private
static
void
unescapeVersion4
(
ParsableByteArray
frame
,
boolean
unsignedIntDataSizeHack
)
{
frame
.
setPosition
(
0
);
byte
[]
bytes
=
frame
.
data
;
while
(
frame
.
bytesLeft
()
>=
10
)
{
if
(
frame
.
readInt
()
==
0
)
{
return
;
}
int
dataSize
=
unsignedIntDataSizeHack
?
frame
.
readUnsignedIntToInt
()
:
frame
.
readSynchSafeInt
();
int
flags
=
frame
.
readUnsignedShort
();
int
previousFlags
=
flags
;
if
((
flags
&
1
)
!=
0
)
{
// Strip data length indicator.
int
offset
=
frame
.
getPosition
();
System
.
arraycopy
(
bytes
,
offset
+
4
,
bytes
,
offset
,
frame
.
bytesLeft
()
-
4
);
dataSize
-=
4
;
flags
&=
~
1
;
frame
.
setLimit
(
frame
.
limit
()
-
4
);
}
if
((
flags
&
2
)
!=
0
)
{
// Unescape 0xFF00 to 0xFF in the next dataSize bytes.
int
readOffset
=
frame
.
getPosition
()
+
1
;
int
writeOffset
=
readOffset
;
for
(
int
i
=
0
;
i
+
1
<
dataSize
;
i
++)
{
if
((
bytes
[
readOffset
-
1
]
&
0xFF
)
==
0xFF
&&
bytes
[
readOffset
]
==
0
)
{
readOffset
++;
dataSize
--;
}
bytes
[
writeOffset
++]
=
bytes
[
readOffset
++];
}
frame
.
setLimit
(
frame
.
limit
()
-
(
readOffset
-
writeOffset
));
System
.
arraycopy
(
bytes
,
readOffset
,
bytes
,
writeOffset
,
frame
.
bytesLeft
()
-
readOffset
);
flags
&=
~
2
;
}
if
(
flags
!=
previousFlags
||
unsignedIntDataSizeHack
)
{
int
dataSizeOffset
=
frame
.
getPosition
()
-
6
;
writeSyncSafeInteger
(
bytes
,
dataSizeOffset
,
dataSize
);
bytes
[
dataSizeOffset
+
4
]
=
(
byte
)
(
flags
>>
8
);
bytes
[
dataSizeOffset
+
5
]
=
(
byte
)
(
flags
&
0xFF
);
}
frame
.
skipBytes
(
dataSize
);
}
}
private
static
void
writeSyncSafeInteger
(
byte
[]
bytes
,
int
offset
,
int
value
)
{
bytes
[
offset
]
=
(
byte
)
((
value
>>
21
)
&
0x7F
);
bytes
[
offset
+
1
]
=
(
byte
)
((
value
>>
14
)
&
0x7F
);
bytes
[
offset
+
2
]
=
(
byte
)
((
value
>>
7
)
&
0x7F
);
bytes
[
offset
+
3
]
=
(
byte
)
(
value
&
0x7F
);
}
private
Id3Util
()
{}
}
library/src/main/java/com/google/android/exoplayer2/extractor/mp3/Mp3Extractor.java
View file @
ced03e9a
...
...
@@ -27,6 +27,8 @@ import com.google.android.exoplayer2.extractor.MpegAudioHeader;
import
com.google.android.exoplayer2.extractor.PositionHolder
;
import
com.google.android.exoplayer2.extractor.SeekMap
;
import
com.google.android.exoplayer2.extractor.TrackOutput
;
import
com.google.android.exoplayer2.metadata.Metadata
;
import
com.google.android.exoplayer2.metadata.id3.Id3Decoder
;
import
com.google.android.exoplayer2.util.ParsableByteArray
;
import
com.google.android.exoplayer2.util.Util
;
import
java.io.EOFException
;
...
...
@@ -57,6 +59,10 @@ 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
;
/**
* Maximum length of data read into {@link #scratch}.
*/
private
static
final
int
SCRATCH_LENGTH
=
10
;
/**
* Mask that includes the audio header values that must match between frames.
...
...
@@ -77,6 +83,7 @@ public final class Mp3Extractor implements Extractor {
private
int
synchronizedHeaderData
;
private
Metadata
metadata
;
private
Seeker
seeker
;
private
long
basisTimeUs
;
private
long
samplesRead
;
...
...
@@ -97,7 +104,7 @@ public final class Mp3Extractor implements Extractor {
*/
public
Mp3Extractor
(
long
forcedFirstSampleTimestampUs
)
{
this
.
forcedFirstSampleTimestampUs
=
forcedFirstSampleTimestampUs
;
scratch
=
new
ParsableByteArray
(
4
);
scratch
=
new
ParsableByteArray
(
SCRATCH_LENGTH
);
synchronizedHeader
=
new
MpegAudioHeader
();
gaplessInfoHolder
=
new
GaplessInfoHolder
();
basisTimeUs
=
C
.
TIME_UNSET
;
...
...
@@ -144,7 +151,7 @@ public final class Mp3Extractor implements Extractor {
trackOutput
.
format
(
Format
.
createAudioSampleFormat
(
null
,
synchronizedHeader
.
mimeType
,
null
,
Format
.
NO_VALUE
,
MpegAudioHeader
.
MAX_FRAME_SIZE_BYTES
,
synchronizedHeader
.
channels
,
synchronizedHeader
.
sampleRate
,
Format
.
NO_VALUE
,
gaplessInfoHolder
.
encoderDelay
,
gaplessInfoHolder
.
encoderPadding
,
null
,
null
,
0
,
null
));
gaplessInfoHolder
.
encoderPadding
,
null
,
null
,
0
,
null
,
metadata
));
}
return
readSample
(
input
);
}
...
...
@@ -199,7 +206,7 @@ public final class Mp3Extractor implements Extractor {
int
searchLimitBytes
=
sniffing
?
MAX_SNIFF_BYTES
:
MAX_SYNC_BYTES
;
input
.
resetPeekPosition
();
if
(
input
.
getPosition
()
==
0
)
{
Id3Util
.
parseId3
(
input
,
gaplessInfoHolder
);
peekId3Data
(
input
);
peekedId3Bytes
=
(
int
)
input
.
getPeekPosition
();
if
(!
sniffing
)
{
input
.
skipFully
(
peekedId3Bytes
);
...
...
@@ -254,6 +261,45 @@ public final class Mp3Extractor implements Extractor {
}
/**
* Peeks ID3 data from the input, including gapless playback information.
*
* @param input The {@link ExtractorInput} from which data should be peeked.
* @throws IOException If an error occurred peeking from the input.
* @throws InterruptedException If the thread was interrupted.
*/
private
void
peekId3Data
(
ExtractorInput
input
)
throws
IOException
,
InterruptedException
{
int
peekedId3Bytes
=
0
;
while
(
true
)
{
input
.
peekFully
(
scratch
.
data
,
0
,
Id3Decoder
.
ID3_HEADER_LENGTH
);
scratch
.
setPosition
(
0
);
if
(
scratch
.
readUnsignedInt24
()
!=
Id3Decoder
.
ID3_TAG
)
{
// Not an ID3 tag.
break
;
}
scratch
.
skipBytes
(
3
);
// Skip major version, minor version and flags.
int
framesLength
=
scratch
.
readSynchSafeInt
();
int
tagLength
=
Id3Decoder
.
ID3_HEADER_LENGTH
+
framesLength
;
if
(
metadata
==
null
)
{
byte
[]
id3Data
=
new
byte
[
tagLength
];
System
.
arraycopy
(
scratch
.
data
,
0
,
id3Data
,
0
,
Id3Decoder
.
ID3_HEADER_LENGTH
);
input
.
peekFully
(
id3Data
,
Id3Decoder
.
ID3_HEADER_LENGTH
,
framesLength
);
metadata
=
new
Id3Decoder
().
decode
(
id3Data
,
tagLength
);
if
(
metadata
!=
null
)
{
gaplessInfoHolder
.
setFromMetadata
(
metadata
);
}
}
else
{
input
.
advancePeekPosition
(
framesLength
);
}
peekedId3Bytes
+=
tagLength
;
}
input
.
resetPeekPosition
();
input
.
advancePeekPosition
(
peekedId3Bytes
);
}
/**
* 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.
...
...
library/src/main/java/com/google/android/exoplayer2/extractor/mp4/Atom.java
View file @
ced03e9a
...
...
@@ -132,7 +132,6 @@ import java.util.List;
public
static
final
int
TYPE_vp08
=
Util
.
getIntegerCodeForString
(
"vp08"
);
public
static
final
int
TYPE_vp09
=
Util
.
getIntegerCodeForString
(
"vp09"
);
public
static
final
int
TYPE_vpcC
=
Util
.
getIntegerCodeForString
(
"vpcC"
);
public
static
final
int
TYPE_DASHES
=
Util
.
getIntegerCodeForString
(
"----"
);
public
final
int
type
;
...
...
@@ -299,7 +298,7 @@ import java.util.List;
* @return The corresponding four character string.
*/
public
static
String
getAtomTypeString
(
int
type
)
{
return
""
+
(
char
)
(
type
>>
24
)
return
""
+
(
char
)
(
(
type
>>
24
)
&
0xFF
)
+
(
char
)
((
type
>>
16
)
&
0xFF
)
+
(
char
)
((
type
>>
8
)
&
0xFF
)
+
(
char
)
(
type
&
0xFF
);
...
...
library/src/main/java/com/google/android/exoplayer2/extractor/mp4/AtomParsers.java
View file @
ced03e9a
...
...
@@ -23,6 +23,7 @@ import com.google.android.exoplayer2.ParserException;
import
com.google.android.exoplayer2.audio.Ac3Util
;
import
com.google.android.exoplayer2.drm.DrmInitData
;
import
com.google.android.exoplayer2.extractor.GaplessInfoHolder
;
import
com.google.android.exoplayer2.metadata.Metadata
;
import
com.google.android.exoplayer2.util.Assertions
;
import
com.google.android.exoplayer2.util.CodecSpecificDataUtil
;
import
com.google.android.exoplayer2.util.MimeTypes
;
...
...
@@ -30,6 +31,7 @@ import com.google.android.exoplayer2.util.ParsableByteArray;
import
com.google.android.exoplayer2.util.Util
;
import
com.google.android.exoplayer2.video.AvcConfig
;
import
com.google.android.exoplayer2.video.HevcConfig
;
import
java.util.ArrayList
;
import
java.util.Arrays
;
import
java.util.Collections
;
import
java.util.List
;
...
...
@@ -400,80 +402,54 @@ import java.util.List;
*
* @param udtaAtom The udta (user data) atom to decode.
* @param isQuickTime True for QuickTime media. False otherwise.
* @
param out {@link GaplessInfoHolder} to populate with gapless playback information
.
* @
return Parsed metadata, or null
.
*/
public
static
void
parseUdta
(
Atom
.
LeafAtom
udtaAtom
,
boolean
isQuickTime
,
GaplessInfoHolder
out
)
{
public
static
Metadata
parseUdta
(
Atom
.
LeafAtom
udtaAtom
,
boolean
isQuickTime
)
{
if
(
isQuickTime
)
{
// Meta boxes are regular boxes rather than full boxes in QuickTime. For now, don't try and
// decode one.
return
;
return
null
;
}
ParsableByteArray
udtaData
=
udtaAtom
.
data
;
udtaData
.
setPosition
(
Atom
.
HEADER_SIZE
);
while
(
udtaData
.
bytesLeft
()
>=
Atom
.
HEADER_SIZE
)
{
int
atomPosition
=
udtaData
.
getPosition
();
int
atomSize
=
udtaData
.
readInt
();
int
atomType
=
udtaData
.
readInt
();
if
(
atomType
==
Atom
.
TYPE_meta
)
{
udtaData
.
setPosition
(
udtaData
.
getPosition
()
-
Atom
.
HEADER_SIZE
);
udtaData
.
setLimit
(
udtaData
.
getPosition
()
+
atomSize
);
parseMetaAtom
(
udtaData
,
out
);
break
;
udtaData
.
setPosition
(
atomPosition
);
return
parseMetaAtom
(
udtaData
,
atomPosition
+
atomSize
);
}
udtaData
.
skipBytes
(
atomSize
-
Atom
.
HEADER_SIZE
);
}
return
null
;
}
private
static
void
parseMetaAtom
(
ParsableByteArray
data
,
GaplessInfoHolder
ou
t
)
{
da
ta
.
skipBytes
(
Atom
.
FULL_HEADER_SIZE
);
ParsableByteArray
ilst
=
new
ParsableByteArray
();
while
(
data
.
bytesLeft
()
>=
Atom
.
HEADER_SIZE
)
{
int
payloadSize
=
data
.
readInt
()
-
Atom
.
HEADER_SIZE
;
int
atomType
=
da
ta
.
readInt
();
private
static
Metadata
parseMetaAtom
(
ParsableByteArray
meta
,
int
limi
t
)
{
me
ta
.
skipBytes
(
Atom
.
FULL_HEADER_SIZE
);
while
(
meta
.
getPosition
()
<
limit
)
{
int
atomPosition
=
meta
.
getPosition
();
int
atomSize
=
meta
.
readInt
()
;
int
atomType
=
me
ta
.
readInt
();
if
(
atomType
==
Atom
.
TYPE_ilst
)
{
ilst
.
reset
(
data
.
data
,
data
.
getPosition
()
+
payloadSize
);
ilst
.
setPosition
(
data
.
getPosition
());
parseIlst
(
ilst
,
out
);
if
(
out
.
hasGaplessInfo
())
{
return
;
}
}
data
.
skipBytes
(
payloadSize
);
}
}
private
static
void
parseIlst
(
ParsableByteArray
ilst
,
GaplessInfoHolder
out
)
{
while
(
ilst
.
bytesLeft
()
>
0
)
{
int
position
=
ilst
.
getPosition
();
int
endPosition
=
position
+
ilst
.
readInt
();
int
type
=
ilst
.
readInt
();
if
(
type
==
Atom
.
TYPE_DASHES
)
{
String
lastCommentMean
=
null
;
String
lastCommentName
=
null
;
String
lastCommentData
=
null
;
while
(
ilst
.
getPosition
()
<
endPosition
)
{
int
length
=
ilst
.
readInt
()
-
Atom
.
FULL_HEADER_SIZE
;
int
key
=
ilst
.
readInt
();
ilst
.
skipBytes
(
4
);
if
(
key
==
Atom
.
TYPE_mean
)
{
lastCommentMean
=
ilst
.
readString
(
length
);
}
else
if
(
key
==
Atom
.
TYPE_name
)
{
lastCommentName
=
ilst
.
readString
(
length
);
}
else
if
(
key
==
Atom
.
TYPE_data
)
{
ilst
.
skipBytes
(
4
);
lastCommentData
=
ilst
.
readString
(
length
-
4
);
}
else
{
ilst
.
skipBytes
(
length
);
meta
.
setPosition
(
atomPosition
);
return
parseIlst
(
meta
,
atomPosition
+
atomSize
);
}
meta
.
skipBytes
(
atomSize
-
Atom
.
HEADER_SIZE
);
}
if
(
lastCommentName
!=
null
&&
lastCommentData
!=
null
&&
"com.apple.iTunes"
.
equals
(
lastCommentMean
))
{
out
.
setFromComment
(
lastCommentName
,
lastCommentData
);
break
;
return
null
;
}
}
else
{
ilst
.
setPosition
(
endPosition
);
private
static
Metadata
parseIlst
(
ParsableByteArray
ilst
,
int
limit
)
{
ilst
.
skipBytes
(
Atom
.
HEADER_SIZE
);
ArrayList
<
Metadata
.
Entry
>
entries
=
new
ArrayList
<>();
while
(
ilst
.
getPosition
()
<
limit
)
{
Metadata
.
Entry
entry
=
MetadataUtil
.
parseIlstElement
(
ilst
);
if
(
entry
!=
null
)
{
entries
.
add
(
entry
);
}
}
return
entries
.
isEmpty
()
?
null
:
new
Metadata
(
entries
);
}
/**
...
...
@@ -484,12 +460,9 @@ import java.util.List;
*/
private
static
long
parseMvhd
(
ParsableByteArray
mvhd
)
{
mvhd
.
setPosition
(
Atom
.
HEADER_SIZE
);
int
fullAtom
=
mvhd
.
readInt
();
int
version
=
Atom
.
parseFullAtomVersion
(
fullAtom
);
mvhd
.
skipBytes
(
version
==
0
?
8
:
16
);
return
mvhd
.
readUnsignedInt
();
}
...
...
library/src/main/java/com/google/android/exoplayer2/extractor/mp4/MetadataUtil.java
0 → 100644
View file @
ced03e9a
/*
* Copyright (C) 2016 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
.
exoplayer2
.
extractor
.
mp4
;
import
android.util.Log
;
import
com.google.android.exoplayer2.metadata.Metadata
;
import
com.google.android.exoplayer2.metadata.id3.ApicFrame
;
import
com.google.android.exoplayer2.metadata.id3.CommentFrame
;
import
com.google.android.exoplayer2.metadata.id3.Id3Frame
;
import
com.google.android.exoplayer2.metadata.id3.TextInformationFrame
;
import
com.google.android.exoplayer2.util.ParsableByteArray
;
import
com.google.android.exoplayer2.util.Util
;
/**
* Parses metadata items stored in ilst atoms.
*/
/* package */
final
class
MetadataUtil
{
private
static
final
String
TAG
=
"MetadataUtil"
;
// Codes that start with the copyright character (omitted) and have equivalent ID3 frames.
private
static
final
int
SHORT_TYPE_NAME_1
=
Util
.
getIntegerCodeForString
(
"nam"
);
private
static
final
int
SHORT_TYPE_NAME_2
=
Util
.
getIntegerCodeForString
(
"trk"
);
private
static
final
int
SHORT_TYPE_COMMENT
=
Util
.
getIntegerCodeForString
(
"cmt"
);
private
static
final
int
SHORT_TYPE_YEAR
=
Util
.
getIntegerCodeForString
(
"day"
);
private
static
final
int
SHORT_TYPE_ARTIST
=
Util
.
getIntegerCodeForString
(
"ART"
);
private
static
final
int
SHORT_TYPE_ENCODER
=
Util
.
getIntegerCodeForString
(
"too"
);
private
static
final
int
SHORT_TYPE_ALBUM
=
Util
.
getIntegerCodeForString
(
"alb"
);
private
static
final
int
SHORT_TYPE_COMPOSER_1
=
Util
.
getIntegerCodeForString
(
"com"
);
private
static
final
int
SHORT_TYPE_COMPOSER_2
=
Util
.
getIntegerCodeForString
(
"wrt"
);
private
static
final
int
SHORT_TYPE_LYRICS
=
Util
.
getIntegerCodeForString
(
"lyr"
);
private
static
final
int
SHORT_TYPE_GENRE
=
Util
.
getIntegerCodeForString
(
"gen"
);
// Codes that have equivalent ID3 frames.
private
static
final
int
TYPE_COVER_ART
=
Util
.
getIntegerCodeForString
(
"covr"
);
private
static
final
int
TYPE_GENRE
=
Util
.
getIntegerCodeForString
(
"gnre"
);
private
static
final
int
TYPE_GROUPING
=
Util
.
getIntegerCodeForString
(
"grp"
);
private
static
final
int
TYPE_DISK_NUMBER
=
Util
.
getIntegerCodeForString
(
"disk"
);
private
static
final
int
TYPE_TRACK_NUMBER
=
Util
.
getIntegerCodeForString
(
"trkn"
);
private
static
final
int
TYPE_TEMPO
=
Util
.
getIntegerCodeForString
(
"tmpo"
);
private
static
final
int
TYPE_COMPILATION
=
Util
.
getIntegerCodeForString
(
"cpil"
);
private
static
final
int
TYPE_ALBUM_ARTIST
=
Util
.
getIntegerCodeForString
(
"aART"
);
private
static
final
int
TYPE_SORT_TRACK_NAME
=
Util
.
getIntegerCodeForString
(
"sonm"
);
private
static
final
int
TYPE_SORT_ALBUM
=
Util
.
getIntegerCodeForString
(
"soal"
);
private
static
final
int
TYPE_SORT_ARTIST
=
Util
.
getIntegerCodeForString
(
"soar"
);
private
static
final
int
TYPE_SORT_ALBUM_ARTIST
=
Util
.
getIntegerCodeForString
(
"soaa"
);
private
static
final
int
TYPE_SORT_COMPOSER
=
Util
.
getIntegerCodeForString
(
"soco"
);
// Types that do not have equivalent ID3 frames.
private
static
final
int
TYPE_RATING
=
Util
.
getIntegerCodeForString
(
"rtng"
);
private
static
final
int
TYPE_GAPLESS_ALBUM
=
Util
.
getIntegerCodeForString
(
"pgap"
);
private
static
final
int
TYPE_TV_SORT_SHOW
=
Util
.
getIntegerCodeForString
(
"sosn"
);
private
static
final
int
TYPE_TV_SHOW
=
Util
.
getIntegerCodeForString
(
"tvsh"
);
// Type for items that are intended for internal use by the player.
private
static
final
int
TYPE_INTERNAL
=
Util
.
getIntegerCodeForString
(
"----"
);
// Standard genres.
private
static
final
String
[]
STANDARD_GENRES
=
new
String
[]
{
// These are the official ID3v1 genres.
"Blues"
,
"Classic Rock"
,
"Country"
,
"Dance"
,
"Disco"
,
"Funk"
,
"Grunge"
,
"Hip-Hop"
,
"Jazz"
,
"Metal"
,
"New Age"
,
"Oldies"
,
"Other"
,
"Pop"
,
"R&B"
,
"Rap"
,
"Reggae"
,
"Rock"
,
"Techno"
,
"Industrial"
,
"Alternative"
,
"Ska"
,
"Death Metal"
,
"Pranks"
,
"Soundtrack"
,
"Euro-Techno"
,
"Ambient"
,
"Trip-Hop"
,
"Vocal"
,
"Jazz+Funk"
,
"Fusion"
,
"Trance"
,
"Classical"
,
"Instrumental"
,
"Acid"
,
"House"
,
"Game"
,
"Sound Clip"
,
"Gospel"
,
"Noise"
,
"AlternRock"
,
"Bass"
,
"Soul"
,
"Punk"
,
"Space"
,
"Meditative"
,
"Instrumental Pop"
,
"Instrumental Rock"
,
"Ethnic"
,
"Gothic"
,
"Darkwave"
,
"Techno-Industrial"
,
"Electronic"
,
"Pop-Folk"
,
"Eurodance"
,
"Dream"
,
"Southern Rock"
,
"Comedy"
,
"Cult"
,
"Gangsta"
,
"Top 40"
,
"Christian Rap"
,
"Pop/Funk"
,
"Jungle"
,
"Native American"
,
"Cabaret"
,
"New Wave"
,
"Psychadelic"
,
"Rave"
,
"Showtunes"
,
"Trailer"
,
"Lo-Fi"
,
"Tribal"
,
"Acid Punk"
,
"Acid Jazz"
,
"Polka"
,
"Retro"
,
"Musical"
,
"Rock & Roll"
,
"Hard Rock"
,
// These were made up by the authors of Winamp and later added to the ID3 spec.
"Folk"
,
"Folk-Rock"
,
"National Folk"
,
"Swing"
,
"Fast Fusion"
,
"Bebob"
,
"Latin"
,
"Revival"
,
"Celtic"
,
"Bluegrass"
,
"Avantgarde"
,
"Gothic Rock"
,
"Progressive Rock"
,
"Psychedelic Rock"
,
"Symphonic Rock"
,
"Slow Rock"
,
"Big Band"
,
"Chorus"
,
"Easy Listening"
,
"Acoustic"
,
"Humour"
,
"Speech"
,
"Chanson"
,
"Opera"
,
"Chamber Music"
,
"Sonata"
,
"Symphony"
,
"Booty Bass"
,
"Primus"
,
"Porn Groove"
,
"Satire"
,
"Slow Jam"
,
"Club"
,
"Tango"
,
"Samba"
,
"Folklore"
,
"Ballad"
,
"Power Ballad"
,
"Rhythmic Soul"
,
"Freestyle"
,
"Duet"
,
"Punk Rock"
,
"Drum Solo"
,
"A capella"
,
"Euro-House"
,
"Dance Hall"
,
// These were med up by the authors of Winamp but have not been added to the ID3 spec.
"Goa"
,
"Drum & Bass"
,
"Club-House"
,
"Hardcore"
,
"Terror"
,
"Indie"
,
"BritPop"
,
"Negerpunk"
,
"Polsk Punk"
,
"Beat"
,
"Christian Gangsta Rap"
,
"Heavy Metal"
,
"Black Metal"
,
"Crossover"
,
"Contemporary Christian"
,
"Christian Rock"
,
"Merengue"
,
"Salsa"
,
"Thrash Metal"
,
"Anime"
,
"Jpop"
,
"Synthpop"
};
private
static
final
String
LANGUAGE_UNDEFINED
=
"und"
;
private
MetadataUtil
()
{}
/**
* Parses a single ilst element from a {@link ParsableByteArray}. The element is read starting
* from the current position of the {@link ParsableByteArray}, and the position is advanced by
* the size of the element. The position is advanced even if the element's type is unrecognized.
*
* @param ilst Holds the data to be parsed.
* @return The parsed element, or null if the element's type was not recognized.
*/
public
static
Metadata
.
Entry
parseIlstElement
(
ParsableByteArray
ilst
)
{
int
position
=
ilst
.
getPosition
();
int
endPosition
=
position
+
ilst
.
readInt
();
int
type
=
ilst
.
readInt
();
int
typeTopByte
=
(
type
>>
24
)
&
0xFF
;
try
{
if
(
typeTopByte
==
'\
u00A9
'
/* Copyright char */
||
typeTopByte
==
'\
uFFFD
'
/* Replacement char */
)
{
int
shortType
=
type
&
0x00FFFFFF
;
if
(
shortType
==
SHORT_TYPE_COMMENT
)
{
return
parseCommentAttribute
(
type
,
ilst
);
}
else
if
(
shortType
==
SHORT_TYPE_NAME_1
||
shortType
==
SHORT_TYPE_NAME_2
)
{
return
parseTextAttribute
(
type
,
"TIT2"
,
ilst
);
}
else
if
(
shortType
==
SHORT_TYPE_COMPOSER_1
||
shortType
==
SHORT_TYPE_COMPOSER_2
)
{
return
parseTextAttribute
(
type
,
"TCOM"
,
ilst
);
}
else
if
(
shortType
==
SHORT_TYPE_YEAR
)
{
return
parseTextAttribute
(
type
,
"TDRC"
,
ilst
);
}
else
if
(
shortType
==
SHORT_TYPE_ARTIST
)
{
return
parseTextAttribute
(
type
,
"TPE1"
,
ilst
);
}
else
if
(
shortType
==
SHORT_TYPE_ENCODER
)
{
return
parseTextAttribute
(
type
,
"TSSE"
,
ilst
);
}
else
if
(
shortType
==
SHORT_TYPE_ALBUM
)
{
return
parseTextAttribute
(
type
,
"TALB"
,
ilst
);
}
else
if
(
shortType
==
SHORT_TYPE_LYRICS
)
{
return
parseTextAttribute
(
type
,
"USLT"
,
ilst
);
}
else
if
(
shortType
==
SHORT_TYPE_GENRE
)
{
return
parseTextAttribute
(
type
,
"TCON"
,
ilst
);
}
else
if
(
shortType
==
TYPE_GROUPING
)
{
return
parseTextAttribute
(
type
,
"TIT1"
,
ilst
);
}
}
else
if
(
type
==
TYPE_GENRE
)
{
return
parseStandardGenreAttribute
(
ilst
);
}
else
if
(
type
==
TYPE_DISK_NUMBER
)
{
return
parseIndexAndCountAttribute
(
type
,
"TPOS"
,
ilst
);
}
else
if
(
type
==
TYPE_TRACK_NUMBER
)
{
return
parseIndexAndCountAttribute
(
type
,
"TRCK"
,
ilst
);
}
else
if
(
type
==
TYPE_TEMPO
)
{
return
parseUint8Attribute
(
type
,
"TBPM"
,
ilst
,
true
,
false
);
}
else
if
(
type
==
TYPE_COMPILATION
)
{
return
parseUint8Attribute
(
type
,
"TCMP"
,
ilst
,
true
,
true
);
}
else
if
(
type
==
TYPE_COVER_ART
)
{
return
parseCoverArt
(
ilst
);
}
else
if
(
type
==
TYPE_ALBUM_ARTIST
)
{
return
parseTextAttribute
(
type
,
"TPE2"
,
ilst
);
}
else
if
(
type
==
TYPE_SORT_TRACK_NAME
)
{
return
parseTextAttribute
(
type
,
"TSOT"
,
ilst
);
}
else
if
(
type
==
TYPE_SORT_ALBUM
)
{
return
parseTextAttribute
(
type
,
"TSO2"
,
ilst
);
}
else
if
(
type
==
TYPE_SORT_ARTIST
)
{
return
parseTextAttribute
(
type
,
"TSOA"
,
ilst
);
}
else
if
(
type
==
TYPE_SORT_ALBUM_ARTIST
)
{
return
parseTextAttribute
(
type
,
"TSOP"
,
ilst
);
}
else
if
(
type
==
TYPE_SORT_COMPOSER
)
{
return
parseTextAttribute
(
type
,
"TSOC"
,
ilst
);
}
else
if
(
type
==
TYPE_RATING
)
{
return
parseUint8Attribute
(
type
,
"ITUNESADVISORY"
,
ilst
,
false
,
false
);
}
else
if
(
type
==
TYPE_GAPLESS_ALBUM
)
{
return
parseUint8Attribute
(
type
,
"ITUNESGAPLESS"
,
ilst
,
false
,
true
);
}
else
if
(
type
==
TYPE_TV_SORT_SHOW
)
{
return
parseTextAttribute
(
type
,
"TVSHOWSORT"
,
ilst
);
}
else
if
(
type
==
TYPE_TV_SHOW
)
{
return
parseTextAttribute
(
type
,
"TVSHOW"
,
ilst
);
}
else
if
(
type
==
TYPE_INTERNAL
)
{
return
parseInternalAttribute
(
ilst
,
endPosition
);
}
Log
.
d
(
TAG
,
"Skipped unknown metadata entry: "
+
Atom
.
getAtomTypeString
(
type
));
return
null
;
}
finally
{
ilst
.
setPosition
(
endPosition
);
}
}
private
static
TextInformationFrame
parseTextAttribute
(
int
type
,
String
id
,
ParsableByteArray
data
)
{
int
atomSize
=
data
.
readInt
();
int
atomType
=
data
.
readInt
();
if
(
atomType
==
Atom
.
TYPE_data
)
{
data
.
skipBytes
(
8
);
// version (1), flags (3), empty (4)
String
value
=
data
.
readNullTerminatedString
(
atomSize
-
16
);
return
new
TextInformationFrame
(
id
,
value
);
}
Log
.
w
(
TAG
,
"Failed to parse text attribute: "
+
Atom
.
getAtomTypeString
(
type
));
return
null
;
}
private
static
CommentFrame
parseCommentAttribute
(
int
type
,
ParsableByteArray
data
)
{
int
atomSize
=
data
.
readInt
();
int
atomType
=
data
.
readInt
();
if
(
atomType
==
Atom
.
TYPE_data
)
{
data
.
skipBytes
(
8
);
// version (1), flags (3), empty (4)
String
value
=
data
.
readNullTerminatedString
(
atomSize
-
16
);
return
new
CommentFrame
(
LANGUAGE_UNDEFINED
,
value
,
value
);
}
Log
.
w
(
TAG
,
"Failed to parse comment attribute: "
+
Atom
.
getAtomTypeString
(
type
));
return
null
;
}
private
static
Id3Frame
parseUint8Attribute
(
int
type
,
String
id
,
ParsableByteArray
data
,
boolean
isTextInformationFrame
,
boolean
isBoolean
)
{
int
value
=
parseUint8AttributeValue
(
data
);
if
(
isBoolean
)
{
value
=
Math
.
min
(
1
,
value
);
}
if
(
value
>=
0
)
{
return
isTextInformationFrame
?
new
TextInformationFrame
(
id
,
Integer
.
toString
(
value
))
:
new
CommentFrame
(
LANGUAGE_UNDEFINED
,
id
,
Integer
.
toString
(
value
));
}
Log
.
w
(
TAG
,
"Failed to parse uint8 attribute: "
+
Atom
.
getAtomTypeString
(
type
));
return
null
;
}
private
static
TextInformationFrame
parseIndexAndCountAttribute
(
int
type
,
String
attributeName
,
ParsableByteArray
data
)
{
int
atomSize
=
data
.
readInt
();
int
atomType
=
data
.
readInt
();
if
(
atomType
==
Atom
.
TYPE_data
&&
atomSize
>=
22
)
{
data
.
skipBytes
(
10
);
// version (1), flags (3), empty (4), empty (2)
int
index
=
data
.
readUnsignedShort
();
if
(
index
>
0
)
{
String
description
=
""
+
index
;
int
count
=
data
.
readUnsignedShort
();
if
(
count
>
0
)
{
description
+=
"/"
+
count
;
}
return
new
TextInformationFrame
(
attributeName
,
description
);
}
}
Log
.
w
(
TAG
,
"Failed to parse index/count attribute: "
+
Atom
.
getAtomTypeString
(
type
));
return
null
;
}
private
static
TextInformationFrame
parseStandardGenreAttribute
(
ParsableByteArray
data
)
{
int
genreCode
=
parseUint8AttributeValue
(
data
);
String
genreString
=
(
0
<
genreCode
&&
genreCode
<=
STANDARD_GENRES
.
length
)
?
STANDARD_GENRES
[
genreCode
-
1
]
:
null
;
if
(
genreString
!=
null
)
{
return
new
TextInformationFrame
(
"TCON"
,
genreString
);
}
Log
.
w
(
TAG
,
"Failed to parse standard genre code"
);
return
null
;
}
private
static
ApicFrame
parseCoverArt
(
ParsableByteArray
data
)
{
int
atomSize
=
data
.
readInt
();
int
atomType
=
data
.
readInt
();
if
(
atomType
==
Atom
.
TYPE_data
)
{
int
fullVersionInt
=
data
.
readInt
();
int
flags
=
Atom
.
parseFullAtomFlags
(
fullVersionInt
);
String
mimeType
=
flags
==
13
?
"image/jpeg"
:
flags
==
14
?
"image/png"
:
null
;
if
(
mimeType
==
null
)
{
Log
.
w
(
TAG
,
"Unrecognized cover art flags: "
+
flags
);
return
null
;
}
data
.
skipBytes
(
4
);
// empty (4)
byte
[]
pictureData
=
new
byte
[
atomSize
-
16
];
data
.
readBytes
(
pictureData
,
0
,
pictureData
.
length
);
return
new
ApicFrame
(
mimeType
,
null
,
3
/* Cover (front) */
,
pictureData
);
}
Log
.
w
(
TAG
,
"Failed to parse cover art attribute"
);
return
null
;
}
private
static
Id3Frame
parseInternalAttribute
(
ParsableByteArray
data
,
int
endPosition
)
{
String
domain
=
null
;
String
name
=
null
;
int
dataAtomPosition
=
-
1
;
int
dataAtomSize
=
-
1
;
while
(
data
.
getPosition
()
<
endPosition
)
{
int
atomPosition
=
data
.
getPosition
();
int
atomSize
=
data
.
readInt
();
int
atomType
=
data
.
readInt
();
data
.
skipBytes
(
4
);
// version (1), flags (3)
if
(
atomType
==
Atom
.
TYPE_mean
)
{
domain
=
data
.
readNullTerminatedString
(
atomSize
-
12
);
}
else
if
(
atomType
==
Atom
.
TYPE_name
)
{
name
=
data
.
readNullTerminatedString
(
atomSize
-
12
);
}
else
{
if
(
atomType
==
Atom
.
TYPE_data
)
{
dataAtomPosition
=
atomPosition
;
dataAtomSize
=
atomSize
;
}
data
.
skipBytes
(
atomSize
-
12
);
}
}
if
(!
"com.apple.iTunes"
.
equals
(
domain
)
||
!
"iTunSMPB"
.
equals
(
name
)
||
dataAtomPosition
==
-
1
)
{
// We're only interested in iTunSMPB.
return
null
;
}
data
.
setPosition
(
dataAtomPosition
);
data
.
skipBytes
(
16
);
// size (4), type (4), version (1), flags (3), empty (4)
String
value
=
data
.
readNullTerminatedString
(
dataAtomSize
-
16
);
return
new
CommentFrame
(
LANGUAGE_UNDEFINED
,
name
,
value
);
}
private
static
int
parseUint8AttributeValue
(
ParsableByteArray
data
)
{
data
.
skipBytes
(
4
);
// atomSize
int
atomType
=
data
.
readInt
();
if
(
atomType
==
Atom
.
TYPE_data
)
{
data
.
skipBytes
(
8
);
// version (1), flags (3), empty (4)
return
data
.
readUnsignedByte
();
}
Log
.
w
(
TAG
,
"Failed to parse uint8 attribute value"
);
return
-
1
;
}
}
library/src/main/java/com/google/android/exoplayer2/extractor/mp4/Mp4Extractor.java
View file @
ced03e9a
...
...
@@ -27,6 +27,7 @@ import com.google.android.exoplayer2.extractor.PositionHolder;
import
com.google.android.exoplayer2.extractor.SeekMap
;
import
com.google.android.exoplayer2.extractor.TrackOutput
;
import
com.google.android.exoplayer2.extractor.mp4.Atom.ContainerAtom
;
import
com.google.android.exoplayer2.metadata.Metadata
;
import
com.google.android.exoplayer2.util.Assertions
;
import
com.google.android.exoplayer2.util.NalUnitUtil
;
import
com.google.android.exoplayer2.util.ParsableByteArray
;
...
...
@@ -310,10 +311,14 @@ public final class Mp4Extractor implements Extractor, SeekMap {
List
<
Mp4Track
>
tracks
=
new
ArrayList
<>();
long
earliestSampleOffset
=
Long
.
MAX_VALUE
;
Metadata
metadata
=
null
;
GaplessInfoHolder
gaplessInfoHolder
=
new
GaplessInfoHolder
();
Atom
.
LeafAtom
udta
=
moov
.
getLeafAtomOfType
(
Atom
.
TYPE_udta
);
if
(
udta
!=
null
)
{
AtomParsers
.
parseUdta
(
udta
,
isQuickTime
,
gaplessInfoHolder
);
metadata
=
AtomParsers
.
parseUdta
(
udta
,
isQuickTime
);
if
(
metadata
!=
null
)
{
gaplessInfoHolder
.
setFromMetadata
(
metadata
);
}
}
for
(
int
i
=
0
;
i
<
moov
.
containerChildren
.
size
();
i
++)
{
...
...
@@ -340,10 +345,15 @@ public final class Mp4Extractor implements Extractor, SeekMap {
// Allow ten source samples per output sample, like the platform extractor.
int
maxInputSize
=
trackSampleTable
.
maximumSize
+
3
*
10
;
Format
format
=
track
.
format
.
copyWithMaxInputSize
(
maxInputSize
);
if
(
track
.
type
==
C
.
TRACK_TYPE_AUDIO
&&
gaplessInfoHolder
.
hasGaplessInfo
())
{
if
(
track
.
type
==
C
.
TRACK_TYPE_AUDIO
)
{
if
(
gaplessInfoHolder
.
hasGaplessInfo
())
{
format
=
format
.
copyWithGaplessInfo
(
gaplessInfoHolder
.
encoderDelay
,
gaplessInfoHolder
.
encoderPadding
);
}
if
(
metadata
!=
null
)
{
format
=
format
.
copyWithMetadata
(
metadata
);
}
}
mp4Track
.
trackOutput
.
format
(
format
);
durationUs
=
Math
.
max
(
durationUs
,
track
.
durationUs
);
...
...
library/src/main/java/com/google/android/exoplayer2/metadata/Metadata.java
0 → 100644
View file @
ced03e9a
/*
* Copyright (C) 2016 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
.
exoplayer2
.
metadata
;
import
android.os.Parcel
;
import
android.os.Parcelable
;
import
java.util.Arrays
;
import
java.util.List
;
/**
* A collection of metadata entries.
*/
public
final
class
Metadata
implements
Parcelable
{
/**
* A metadata entry.
*/
public
interface
Entry
extends
Parcelable
{}
private
final
Entry
[]
entries
;
/**
* @param entries The metadata entries.
*/
public
Metadata
(
Entry
...
entries
)
{
this
.
entries
=
entries
==
null
?
new
Entry
[
0
]
:
entries
;
}
/**
* @param entries The metadata entries.
*/
public
Metadata
(
List
<?
extends
Entry
>
entries
)
{
if
(
entries
!=
null
)
{
this
.
entries
=
new
Entry
[
entries
.
size
()];
entries
.
toArray
(
this
.
entries
);
}
else
{
this
.
entries
=
new
Entry
[
0
];
}
}
/* package */
Metadata
(
Parcel
in
)
{
entries
=
new
Metadata
.
Entry
[
in
.
readInt
()];
for
(
int
i
=
0
;
i
<
entries
.
length
;
i
++)
{
entries
[
i
]
=
in
.
readParcelable
(
Entry
.
class
.
getClassLoader
());
}
}
/**
* Returns the number of metadata entries.
*/
public
int
length
()
{
return
entries
.
length
;
}
/**
* Returns the entry at the specified index.
*
* @param index The index of the entry.
* @return The entry at the specified index.
*/
public
Metadata
.
Entry
get
(
int
index
)
{
return
entries
[
index
];
}
@Override
public
boolean
equals
(
Object
obj
)
{
if
(
this
==
obj
)
{
return
true
;
}
if
(
obj
==
null
||
getClass
()
!=
obj
.
getClass
())
{
return
false
;
}
Metadata
other
=
(
Metadata
)
obj
;
return
Arrays
.
equals
(
entries
,
other
.
entries
);
}
@Override
public
int
hashCode
()
{
return
Arrays
.
hashCode
(
entries
);
}
@Override
public
int
describeContents
()
{
return
0
;
}
@Override
public
void
writeToParcel
(
Parcel
dest
,
int
flags
)
{
dest
.
writeInt
(
entries
.
length
);
for
(
Entry
entry
:
entries
)
{
dest
.
writeParcelable
(
entry
,
0
);
}
}
public
static
final
Parcelable
.
Creator
<
Metadata
>
CREATOR
=
new
Parcelable
.
Creator
<
Metadata
>()
{
@Override
public
Metadata
createFromParcel
(
Parcel
in
)
{
return
new
Metadata
(
in
);
}
@Override
public
Metadata
[]
newArray
(
int
size
)
{
return
new
Metadata
[
0
];
}
};
}
library/src/main/java/com/google/android/exoplayer2/metadata/MetadataDecoder.java
View file @
ced03e9a
...
...
@@ -17,10 +17,8 @@ package com.google.android.exoplayer2.metadata;
/**
* Decodes metadata from binary data.
*
* @param <T> The type of the metadata.
*/
public
interface
MetadataDecoder
<
T
>
{
public
interface
MetadataDecoder
{
/**
* Checks whether the decoder supports a given mime type.
...
...
@@ -38,6 +36,6 @@ public interface MetadataDecoder<T> {
* @return The decoded metadata object.
* @throws MetadataDecoderException If a problem occurred decoding the data.
*/
T
decode
(
byte
[]
data
,
int
size
)
throws
MetadataDecoderException
;
Metadata
decode
(
byte
[]
data
,
int
size
)
throws
MetadataDecoderException
;
}
library/src/main/java/com/google/android/exoplayer2/metadata/MetadataRenderer.java
View file @
ced03e9a
...
...
@@ -30,38 +30,34 @@ import java.nio.ByteBuffer;
/**
* A renderer for metadata.
*
* @param <T> The type of the metadata.
*/
public
final
class
MetadataRenderer
<
T
>
extends
BaseRenderer
implements
Callback
{
public
final
class
MetadataRenderer
extends
BaseRenderer
implements
Callback
{
/**
* Receives output from a {@link MetadataRenderer}.
*
* @param <T> The type of the metadata.
*/
public
interface
Output
<
T
>
{
public
interface
Output
{
/**
* Called each time there is a metadata associated with current playback time.
*
* @param metadata The metadata.
*/
void
onMetadata
(
T
metadata
);
void
onMetadata
(
Metadata
metadata
);
}
private
static
final
int
MSG_INVOKE_RENDERER
=
0
;
private
final
MetadataDecoder
<
T
>
metadataDecoder
;
private
final
Output
<
T
>
output
;
private
final
MetadataDecoder
metadataDecoder
;
private
final
Output
output
;
private
final
Handler
outputHandler
;
private
final
FormatHolder
formatHolder
;
private
final
DecoderInputBuffer
buffer
;
private
boolean
inputStreamEnded
;
private
long
pendingMetadataTimestamp
;
private
T
pendingMetadata
;
private
Metadata
pendingMetadata
;
/**
* @param output The output.
...
...
@@ -72,8 +68,7 @@ public final class MetadataRenderer<T> extends BaseRenderer implements Callback
* called directly on the player's internal rendering thread.
* @param metadataDecoder A decoder for the metadata.
*/
public
MetadataRenderer
(
Output
<
T
>
output
,
Looper
outputLooper
,
MetadataDecoder
<
T
>
metadataDecoder
)
{
public
MetadataRenderer
(
Output
output
,
Looper
outputLooper
,
MetadataDecoder
metadataDecoder
)
{
super
(
C
.
TRACK_TYPE_METADATA
);
this
.
output
=
Assertions
.
checkNotNull
(
output
);
this
.
outputHandler
=
outputLooper
==
null
?
null
:
new
Handler
(
outputLooper
,
this
);
...
...
@@ -137,7 +132,7 @@ public final class MetadataRenderer<T> extends BaseRenderer implements Callback
return
true
;
}
private
void
invokeRenderer
(
T
metadata
)
{
private
void
invokeRenderer
(
Metadata
metadata
)
{
if
(
outputHandler
!=
null
)
{
outputHandler
.
obtainMessage
(
MSG_INVOKE_RENDERER
,
metadata
).
sendToTarget
();
}
else
{
...
...
@@ -150,13 +145,13 @@ public final class MetadataRenderer<T> extends BaseRenderer implements Callback
public
boolean
handleMessage
(
Message
msg
)
{
switch
(
msg
.
what
)
{
case
MSG_INVOKE_RENDERER:
invokeRendererInternal
((
T
)
msg
.
obj
);
invokeRendererInternal
((
Metadata
)
msg
.
obj
);
return
true
;
}
return
false
;
}
private
void
invokeRendererInternal
(
T
metadata
)
{
private
void
invokeRendererInternal
(
Metadata
metadata
)
{
output
.
onMetadata
(
metadata
);
}
...
...
library/src/main/java/com/google/android/exoplayer2/metadata/id3/ApicFrame.java
View file @
ced03e9a
...
...
@@ -15,6 +15,11 @@
*/
package
com
.
google
.
android
.
exoplayer2
.
metadata
.
id3
;
import
android.os.Parcel
;
import
android.os.Parcelable
;
import
com.google.android.exoplayer2.util.Util
;
import
java.util.Arrays
;
/**
* APIC (Attached Picture) ID3 frame.
*/
...
...
@@ -35,4 +40,58 @@ public final class ApicFrame extends Id3Frame {
this
.
pictureData
=
pictureData
;
}
/* package */
ApicFrame
(
Parcel
in
)
{
super
(
ID
);
mimeType
=
in
.
readString
();
description
=
in
.
readString
();
pictureType
=
in
.
readInt
();
pictureData
=
in
.
createByteArray
();
}
@Override
public
boolean
equals
(
Object
obj
)
{
if
(
this
==
obj
)
{
return
true
;
}
if
(
obj
==
null
||
getClass
()
!=
obj
.
getClass
())
{
return
false
;
}
ApicFrame
other
=
(
ApicFrame
)
obj
;
return
pictureType
==
other
.
pictureType
&&
Util
.
areEqual
(
mimeType
,
other
.
mimeType
)
&&
Util
.
areEqual
(
description
,
other
.
description
)
&&
Arrays
.
equals
(
pictureData
,
other
.
pictureData
);
}
@Override
public
int
hashCode
()
{
int
result
=
17
;
result
=
31
*
result
+
pictureType
;
result
=
31
*
result
+
(
mimeType
!=
null
?
mimeType
.
hashCode
()
:
0
);
result
=
31
*
result
+
(
description
!=
null
?
description
.
hashCode
()
:
0
);
result
=
31
*
result
+
Arrays
.
hashCode
(
pictureData
);
return
result
;
}
@Override
public
void
writeToParcel
(
Parcel
dest
,
int
flags
)
{
dest
.
writeString
(
mimeType
);
dest
.
writeString
(
description
);
dest
.
writeInt
(
pictureType
);
dest
.
writeByteArray
(
pictureData
);
}
public
static
final
Parcelable
.
Creator
<
ApicFrame
>
CREATOR
=
new
Parcelable
.
Creator
<
ApicFrame
>()
{
@Override
public
ApicFrame
createFromParcel
(
Parcel
in
)
{
return
new
ApicFrame
(
in
);
}
@Override
public
ApicFrame
[]
newArray
(
int
size
)
{
return
new
ApicFrame
[
size
];
}
};
}
library/src/main/java/com/google/android/exoplayer2/metadata/id3/BinaryFrame.java
View file @
ced03e9a
...
...
@@ -15,6 +15,10 @@
*/
package
com
.
google
.
android
.
exoplayer2
.
metadata
.
id3
;
import
android.os.Parcel
;
import
android.os.Parcelable
;
import
java.util.Arrays
;
/**
* Binary ID3 frame.
*/
...
...
@@ -22,9 +26,55 @@ public final class BinaryFrame extends Id3Frame {
public
final
byte
[]
data
;
public
BinaryFrame
(
String
type
,
byte
[]
data
)
{
super
(
type
);
public
BinaryFrame
(
String
id
,
byte
[]
data
)
{
super
(
id
);
this
.
data
=
data
;
}
/* package */
BinaryFrame
(
Parcel
in
)
{
super
(
in
.
readString
());
data
=
in
.
createByteArray
();
}
@Override
public
boolean
equals
(
Object
obj
)
{
if
(
this
==
obj
)
{
return
true
;
}
if
(
obj
==
null
||
getClass
()
!=
obj
.
getClass
())
{
return
false
;
}
BinaryFrame
other
=
(
BinaryFrame
)
obj
;
return
id
.
equals
(
other
.
id
)
&&
Arrays
.
equals
(
data
,
other
.
data
);
}
@Override
public
int
hashCode
()
{
int
result
=
17
;
result
=
31
*
result
+
id
.
hashCode
();
result
=
31
*
result
+
Arrays
.
hashCode
(
data
);
return
result
;
}
@Override
public
void
writeToParcel
(
Parcel
dest
,
int
flags
)
{
dest
.
writeString
(
id
);
dest
.
writeByteArray
(
data
);
}
public
static
final
Parcelable
.
Creator
<
BinaryFrame
>
CREATOR
=
new
Parcelable
.
Creator
<
BinaryFrame
>()
{
@Override
public
BinaryFrame
createFromParcel
(
Parcel
in
)
{
return
new
BinaryFrame
(
in
);
}
@Override
public
BinaryFrame
[]
newArray
(
int
size
)
{
return
new
BinaryFrame
[
size
];
}
};
}
library/src/main/java/com/google/android/exoplayer2/metadata/id3/CommentFrame.java
0 → 100644
View file @
ced03e9a
/*
* Copyright (C) 2016 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
.
exoplayer2
.
metadata
.
id3
;
import
android.os.Parcel
;
import
android.os.Parcelable
;
import
com.google.android.exoplayer2.util.Util
;
/**
* Comment ID3 frame.
*/
public
final
class
CommentFrame
extends
Id3Frame
{
public
static
final
String
ID
=
"COMM"
;
public
final
String
language
;
public
final
String
description
;
public
final
String
text
;
public
CommentFrame
(
String
language
,
String
description
,
String
text
)
{
super
(
ID
);
this
.
language
=
language
;
this
.
description
=
description
;
this
.
text
=
text
;
}
/* package */
CommentFrame
(
Parcel
in
)
{
super
(
ID
);
language
=
in
.
readString
();
description
=
in
.
readString
();
text
=
in
.
readString
();
}
@Override
public
boolean
equals
(
Object
obj
)
{
if
(
this
==
obj
)
{
return
true
;
}
if
(
obj
==
null
||
getClass
()
!=
obj
.
getClass
())
{
return
false
;
}
CommentFrame
other
=
(
CommentFrame
)
obj
;
return
Util
.
areEqual
(
description
,
other
.
description
)
&&
Util
.
areEqual
(
language
,
other
.
language
)
&&
Util
.
areEqual
(
text
,
other
.
text
);
}
@Override
public
int
hashCode
()
{
int
result
=
17
;
result
=
31
*
result
+
(
language
!=
null
?
language
.
hashCode
()
:
0
);
result
=
31
*
result
+
(
description
!=
null
?
description
.
hashCode
()
:
0
);
result
=
31
*
result
+
(
text
!=
null
?
text
.
hashCode
()
:
0
);
return
result
;
}
@Override
public
void
writeToParcel
(
Parcel
dest
,
int
flags
)
{
dest
.
writeString
(
id
);
dest
.
writeString
(
language
);
dest
.
writeString
(
text
);
}
public
static
final
Parcelable
.
Creator
<
CommentFrame
>
CREATOR
=
new
Parcelable
.
Creator
<
CommentFrame
>()
{
@Override
public
CommentFrame
createFromParcel
(
Parcel
in
)
{
return
new
CommentFrame
(
in
);
}
@Override
public
CommentFrame
[]
newArray
(
int
size
)
{
return
new
CommentFrame
[
size
];
}
};
}
library/src/main/java/com/google/android/exoplayer2/metadata/id3/GeobFrame.java
View file @
ced03e9a
...
...
@@ -15,6 +15,11 @@
*/
package
com
.
google
.
android
.
exoplayer2
.
metadata
.
id3
;
import
android.os.Parcel
;
import
android.os.Parcelable
;
import
com.google.android.exoplayer2.util.Util
;
import
java.util.Arrays
;
/**
* GEOB (General Encapsulated Object) ID3 frame.
*/
...
...
@@ -35,4 +40,57 @@ public final class GeobFrame extends Id3Frame {
this
.
data
=
data
;
}
/* package */
GeobFrame
(
Parcel
in
)
{
super
(
ID
);
mimeType
=
in
.
readString
();
filename
=
in
.
readString
();
description
=
in
.
readString
();
data
=
in
.
createByteArray
();
}
@Override
public
boolean
equals
(
Object
obj
)
{
if
(
this
==
obj
)
{
return
true
;
}
if
(
obj
==
null
||
getClass
()
!=
obj
.
getClass
())
{
return
false
;
}
GeobFrame
other
=
(
GeobFrame
)
obj
;
return
Util
.
areEqual
(
mimeType
,
other
.
mimeType
)
&&
Util
.
areEqual
(
filename
,
other
.
filename
)
&&
Util
.
areEqual
(
description
,
other
.
description
)
&&
Arrays
.
equals
(
data
,
other
.
data
);
}
@Override
public
int
hashCode
()
{
int
result
=
17
;
result
=
31
*
result
+
(
mimeType
!=
null
?
mimeType
.
hashCode
()
:
0
);
result
=
31
*
result
+
(
filename
!=
null
?
filename
.
hashCode
()
:
0
);
result
=
31
*
result
+
(
description
!=
null
?
description
.
hashCode
()
:
0
);
result
=
31
*
result
+
Arrays
.
hashCode
(
data
);
return
result
;
}
@Override
public
void
writeToParcel
(
Parcel
dest
,
int
flags
)
{
dest
.
writeString
(
mimeType
);
dest
.
writeString
(
filename
);
dest
.
writeString
(
description
);
dest
.
writeByteArray
(
data
);
}
public
static
final
Parcelable
.
Creator
<
GeobFrame
>
CREATOR
=
new
Parcelable
.
Creator
<
GeobFrame
>()
{
@Override
public
GeobFrame
createFromParcel
(
Parcel
in
)
{
return
new
GeobFrame
(
in
);
}
@Override
public
GeobFrame
[]
newArray
(
int
size
)
{
return
new
GeobFrame
[
size
];
}
};
}
library/src/main/java/com/google/android/exoplayer2/metadata/id3/Id3Decoder.java
View file @
ced03e9a
...
...
@@ -15,21 +15,33 @@
*/
package
com
.
google
.
android
.
exoplayer2
.
metadata
.
id3
;
import
android.util.Log
;
import
com.google.android.exoplayer2.metadata.Metadata
;
import
com.google.android.exoplayer2.metadata.MetadataDecoder
;
import
com.google.android.exoplayer2.metadata.MetadataDecoderException
;
import
com.google.android.exoplayer2.util.MimeTypes
;
import
com.google.android.exoplayer2.util.ParsableByteArray
;
import
com.google.android.exoplayer2.util.Util
;
import
java.io.UnsupportedEncodingException
;
import
java.util.ArrayList
;
import
java.util.Arrays
;
import
java.util.Collections
;
import
java.util.List
;
import
java.util.Locale
;
/**
* Decodes
individual TXXX text frames from raw ID3 data
.
* Decodes
ID3 tags
.
*/
public
final
class
Id3Decoder
implements
MetadataDecoder
<
List
<
Id3Frame
>>
{
public
final
class
Id3Decoder
implements
MetadataDecoder
{
private
static
final
String
TAG
=
"Id3Decoder"
;
/**
* The first three bytes of a well formed ID3 tag header.
*/
public
static
final
int
ID3_TAG
=
Util
.
getIntegerCodeForString
(
"ID3"
);
/**
* Length of an ID3 tag header.
*/
public
static
final
int
ID3_HEADER_LENGTH
=
10
;
private
static
final
int
ID3_TEXT_ENCODING_ISO_8859_1
=
0
;
private
static
final
int
ID3_TEXT_ENCODING_UTF_16
=
1
;
...
...
@@ -42,51 +54,46 @@ public final class Id3Decoder implements MetadataDecoder<List<Id3Frame>> {
}
@Override
public
List
<
Id3Frame
>
decode
(
byte
[]
data
,
int
size
)
throws
MetadataDecoderException
{
public
Metadata
decode
(
byte
[]
data
,
int
size
)
{
List
<
Id3Frame
>
id3Frames
=
new
ArrayList
<>();
ParsableByteArray
id3Data
=
new
ParsableByteArray
(
data
,
size
);
int
id3Size
=
decodeId3Header
(
id3Data
);
while
(
id3Size
>
0
)
{
int
frameId0
=
id3Data
.
readUnsignedByte
();
int
frameId1
=
id3Data
.
readUnsignedByte
();
int
frameId2
=
id3Data
.
readUnsignedByte
();
int
frameId3
=
id3Data
.
readUnsignedByte
();
int
frameSize
=
id3Data
.
readSynchSafeInt
();
if
(
frameSize
<=
1
)
{
break
;
Id3Header
id3Header
=
decodeHeader
(
id3Data
);
if
(
id3Header
==
null
)
{
return
null
;
}
// Skip frame flags.
id3Data
.
skipBytes
(
2
);
int
startPosition
=
id3Data
.
getPosition
();
int
framesSize
=
id3Header
.
framesSize
;
if
(
id3Header
.
isUnsynchronized
)
{
framesSize
=
removeUnsynchronization
(
id3Data
,
id3Header
.
framesSize
);
}
id3Data
.
setLimit
(
startPosition
+
framesSize
);
try
{
Id3Frame
frame
;
if
(
frameId0
==
'T'
&&
frameId1
==
'X'
&&
frameId2
==
'X'
&&
frameId3
==
'X'
)
{
frame
=
decodeTxxxFrame
(
id3Data
,
frameSize
);
}
else
if
(
frameId0
==
'P'
&&
frameId1
==
'R'
&&
frameId2
==
'I'
&&
frameId3
==
'V'
)
{
frame
=
decodePrivFrame
(
id3Data
,
frameSize
);
}
else
if
(
frameId0
==
'G'
&&
frameId1
==
'E'
&&
frameId2
==
'O'
&&
frameId3
==
'B'
)
{
frame
=
decodeGeobFrame
(
id3Data
,
frameSize
);
}
else
if
(
frameId0
==
'A'
&&
frameId1
==
'P'
&&
frameId2
==
'I'
&&
frameId3
==
'C'
)
{
frame
=
decodeApicFrame
(
id3Data
,
frameSize
);
}
else
if
(
frameId0
==
'T'
)
{
String
id
=
String
.
format
(
Locale
.
US
,
"%c%c%c%c"
,
frameId0
,
frameId1
,
frameId2
,
frameId3
);
frame
=
decodeTextInformationFrame
(
id3Data
,
frameSize
,
id
);
boolean
unsignedIntFrameSizeHack
=
false
;
if
(
id3Header
.
majorVersion
==
4
)
{
if
(!
validateV4Frames
(
id3Data
,
false
))
{
if
(
validateV4Frames
(
id3Data
,
true
))
{
unsignedIntFrameSizeHack
=
true
;
}
else
{
String
id
=
String
.
format
(
Locale
.
US
,
"%c%c%c%c"
,
frameId0
,
frameId1
,
frameId2
,
frameId3
);
frame
=
decodeBinaryFrame
(
id3Data
,
frameSize
,
id
);
Log
.
w
(
TAG
,
"Failed to validate V4 ID3 tag"
);
return
null
;
}
}
}
int
frameHeaderSize
=
id3Header
.
majorVersion
==
2
?
6
:
10
;
while
(
id3Data
.
bytesLeft
()
>=
frameHeaderSize
)
{
Id3Frame
frame
=
decodeFrame
(
id3Header
.
majorVersion
,
id3Data
,
unsignedIntFrameSizeHack
);
if
(
frame
!=
null
)
{
id3Frames
.
add
(
frame
);
id3Size
-=
frameSize
+
10
/* header size */
;
}
catch
(
UnsupportedEncodingException
e
)
{
throw
new
MetadataDecoderException
(
"Unsupported encoding"
,
e
);
}
}
return
Collections
.
unmodifiableList
(
id3Frames
);
return
new
Metadata
(
id3Frames
);
}
// TODO: Move the following three methods nearer to the bottom of the file.
private
static
int
indexOfEos
(
byte
[]
data
,
int
fromIndex
,
int
encoding
)
{
int
terminationPos
=
indexOfZeroByte
(
data
,
fromIndex
);
...
...
@@ -95,7 +102,7 @@ public final class Id3Decoder implements MetadataDecoder<List<Id3Frame>> {
return
terminationPos
;
}
// Otherwise look for a second zero byte.
// Otherwise
ensure an even index and
look for a second zero byte.
while
(
terminationPos
<
data
.
length
-
1
)
{
if
(
terminationPos
%
2
==
0
&&
data
[
terminationPos
+
1
]
==
(
byte
)
0
)
{
return
terminationPos
;
...
...
@@ -121,38 +128,207 @@ public final class Id3Decoder implements MetadataDecoder<List<Id3Frame>> {
}
/**
* @param id3Buffer A {@link ParsableByteArray} from which data should be read.
* @return The size of ID3 frames in bytes, excluding the header and footer.
* @throws MetadataDecoderException If ID3 file identifier != "ID3".
* @param data A {@link ParsableByteArray} from which the header should be read.
* @return The parsed header, or null if the ID3 tag is unsupported.
*/
private
static
int
decodeId3Header
(
ParsableByteArray
id3Buffer
)
throws
MetadataDecoderException
{
int
id1
=
id3Buffer
.
readUnsignedByte
();
int
id2
=
id3Buffer
.
readUnsignedByte
();
int
id3
=
id3Buffer
.
readUnsignedByte
();
if
(
id1
!=
'I'
||
id2
!=
'D'
||
id3
!=
'3'
)
{
throw
new
MetadataDecoderException
(
String
.
format
(
Locale
.
US
,
"Unexpected ID3 file identifier, expected \"ID3\", actual \"%c%c%c\"."
,
id1
,
id2
,
id3
));
private
static
Id3Header
decodeHeader
(
ParsableByteArray
data
)
{
if
(
data
.
bytesLeft
()
<
ID3_HEADER_LENGTH
)
{
Log
.
w
(
TAG
,
"Data too short to be an ID3 tag"
);
return
null
;
}
int
id
=
data
.
readUnsignedInt24
();
if
(
id
!=
ID3_TAG
)
{
Log
.
w
(
TAG
,
"Unexpected first three bytes of ID3 tag header: "
+
id
);
return
null
;
}
id3Buffer
.
skipBytes
(
2
);
// Skip version.
int
flags
=
id3Buffer
.
readUnsignedByte
();
int
id3Size
=
id3Buffer
.
readSynchSafeInt
();
int
majorVersion
=
data
.
readUnsignedByte
();
data
.
skipBytes
(
1
);
// Skip minor version.
int
flags
=
data
.
readUnsignedByte
();
int
framesSize
=
data
.
readSynchSafeInt
();
// Check if extended header presents.
if
((
flags
&
0x2
)
!=
0
)
{
int
extendedHeaderSize
=
id3Buffer
.
readSynchSafeInt
();
if
(
extendedHeaderSize
>
4
)
{
id3Buffer
.
skipBytes
(
extendedHeaderSize
-
4
);
if
(
majorVersion
==
2
)
{
boolean
isCompressed
=
(
flags
&
0x40
)
!=
0
;
if
(
isCompressed
)
{
Log
.
w
(
TAG
,
"Skipped ID3 tag with majorVersion=2 and undefined compression scheme"
);
return
null
;
}
}
else
if
(
majorVersion
==
3
)
{
boolean
hasExtendedHeader
=
(
flags
&
0x40
)
!=
0
;
if
(
hasExtendedHeader
)
{
int
extendedHeaderSize
=
data
.
readInt
();
// Size excluding size field.
data
.
skipBytes
(
extendedHeaderSize
);
framesSize
-=
(
extendedHeaderSize
+
4
);
}
}
else
if
(
majorVersion
==
4
)
{
boolean
hasExtendedHeader
=
(
flags
&
0x40
)
!=
0
;
if
(
hasExtendedHeader
)
{
int
extendedHeaderSize
=
data
.
readSynchSafeInt
();
// Size including size field.
data
.
skipBytes
(
extendedHeaderSize
-
4
);
framesSize
-=
extendedHeaderSize
;
}
boolean
hasFooter
=
(
flags
&
0x10
)
!=
0
;
if
(
hasFooter
)
{
framesSize
-=
10
;
}
}
else
{
Log
.
w
(
TAG
,
"Skipped ID3 tag with unsupported majorVersion="
+
majorVersion
);
return
null
;
}
// isUnsynchronized is advisory only in version 4. Frame level flags are used instead.
boolean
isUnsynchronized
=
majorVersion
<
4
&&
(
flags
&
0x80
)
!=
0
;
return
new
Id3Header
(
majorVersion
,
isUnsynchronized
,
framesSize
);
}
private
static
boolean
validateV4Frames
(
ParsableByteArray
id3Data
,
boolean
unsignedIntFrameSizeHack
)
{
int
startPosition
=
id3Data
.
getPosition
();
try
{
while
(
id3Data
.
bytesLeft
()
>=
10
)
{
int
id
=
id3Data
.
readInt
();
int
frameSize
=
id3Data
.
readUnsignedIntToInt
();
int
flags
=
id3Data
.
readUnsignedShort
();
if
(
id
==
0
&&
frameSize
==
0
&&
flags
==
0
)
{
return
true
;
}
else
{
if
(!
unsignedIntFrameSizeHack
)
{
// Parse the data size as a synchsafe integer, as per the spec.
if
((
frameSize
&
0x808080
L
)
!=
0
)
{
return
false
;
}
frameSize
=
(
frameSize
&
0xFF
)
|
(((
frameSize
>>
8
)
&
0xFF
)
<<
7
)
|
(((
frameSize
>>
16
)
&
0xFF
)
<<
14
)
|
(((
frameSize
>>
24
)
&
0xFF
)
<<
21
);
}
int
minimumFrameSize
=
0
;
if
((
flags
&
0x0040
)
!=
0
/* hasGroupIdentifier */
)
{
minimumFrameSize
++;
}
if
((
flags
&
0x0001
)
!=
0
/* hasDataLength */
)
{
minimumFrameSize
+=
4
;
}
if
(
frameSize
<
minimumFrameSize
)
{
return
false
;
}
if
(
id3Data
.
bytesLeft
()
<
frameSize
)
{
return
false
;
}
id3Data
.
skipBytes
(
frameSize
);
// flags
}
}
return
true
;
}
finally
{
id3Data
.
setPosition
(
startPosition
);
}
id3Size
-=
extendedHeaderSize
;
}
// Check if footer presents.
if
((
flags
&
0x8
)
!=
0
)
{
id3Size
-=
10
;
private
static
Id3Frame
decodeFrame
(
int
majorVersion
,
ParsableByteArray
id3Data
,
boolean
unsignedIntFrameSizeHack
)
{
int
frameId0
=
id3Data
.
readUnsignedByte
();
int
frameId1
=
id3Data
.
readUnsignedByte
();
int
frameId2
=
id3Data
.
readUnsignedByte
();
int
frameId3
=
majorVersion
>=
3
?
id3Data
.
readUnsignedByte
()
:
0
;
int
frameSize
;
if
(
majorVersion
==
4
)
{
frameSize
=
id3Data
.
readUnsignedIntToInt
();
if
(!
unsignedIntFrameSizeHack
)
{
frameSize
=
(
frameSize
&
0xFF
)
|
(((
frameSize
>>
8
)
&
0xFF
)
<<
7
)
|
(((
frameSize
>>
16
)
&
0xFF
)
<<
14
)
|
(((
frameSize
>>
24
)
&
0xFF
)
<<
21
);
}
}
else
if
(
majorVersion
==
3
)
{
frameSize
=
id3Data
.
readUnsignedIntToInt
();
}
else
/* id3Header.majorVersion == 2 */
{
frameSize
=
id3Data
.
readUnsignedInt24
();
}
int
flags
=
majorVersion
>=
3
?
id3Data
.
readUnsignedShort
()
:
0
;
if
(
frameId0
==
0
&&
frameId1
==
0
&&
frameId2
==
0
&&
frameId3
==
0
&&
frameSize
==
0
&&
flags
==
0
)
{
// We must be reading zero padding at the end of the tag.
id3Data
.
setPosition
(
id3Data
.
limit
());
return
null
;
}
return
id3Size
;
int
nextFramePosition
=
id3Data
.
getPosition
()
+
frameSize
;
if
(
nextFramePosition
>
id3Data
.
limit
())
{
Log
.
w
(
TAG
,
"Frame size exceeds remaining tag data"
);
id3Data
.
setPosition
(
id3Data
.
limit
());
return
null
;
}
// Frame flags.
boolean
isCompressed
=
false
;
boolean
isEncrypted
=
false
;
boolean
isUnsynchronized
=
false
;
boolean
hasDataLength
=
false
;
boolean
hasGroupIdentifier
=
false
;
if
(
majorVersion
==
3
)
{
isCompressed
=
(
flags
&
0x0080
)
!=
0
;
isEncrypted
=
(
flags
&
0x0040
)
!=
0
;
hasGroupIdentifier
=
(
flags
&
0x0020
)
!=
0
;
hasDataLength
=
isCompressed
;
}
else
if
(
majorVersion
==
4
)
{
hasGroupIdentifier
=
(
flags
&
0x0040
)
!=
0
;
isCompressed
=
(
flags
&
0x0008
)
!=
0
;
isEncrypted
=
(
flags
&
0x0004
)
!=
0
;
isUnsynchronized
=
(
flags
&
0x0002
)
!=
0
;
hasDataLength
=
(
flags
&
0x0001
)
!=
0
;
}
if
(
isCompressed
||
isEncrypted
)
{
Log
.
w
(
TAG
,
"Skipping unsupported compressed or encrypted frame"
);
id3Data
.
setPosition
(
nextFramePosition
);
return
null
;
}
if
(
hasGroupIdentifier
)
{
frameSize
--;
id3Data
.
skipBytes
(
1
);
}
if
(
hasDataLength
)
{
frameSize
-=
4
;
id3Data
.
skipBytes
(
4
);
}
if
(
isUnsynchronized
)
{
frameSize
=
removeUnsynchronization
(
id3Data
,
frameSize
);
}
try
{
Id3Frame
frame
;
if
(
frameId0
==
'T'
&&
frameId1
==
'X'
&&
frameId2
==
'X'
&&
(
majorVersion
==
2
||
frameId3
==
'X'
))
{
frame
=
decodeTxxxFrame
(
id3Data
,
frameSize
);
}
else
if
(
frameId0
==
'P'
&&
frameId1
==
'R'
&&
frameId2
==
'I'
&&
frameId3
==
'V'
)
{
frame
=
decodePrivFrame
(
id3Data
,
frameSize
);
}
else
if
(
frameId0
==
'G'
&&
frameId1
==
'E'
&&
frameId2
==
'O'
&&
(
frameId3
==
'B'
||
majorVersion
==
2
))
{
frame
=
decodeGeobFrame
(
id3Data
,
frameSize
);
}
else
if
(
majorVersion
==
2
?
(
frameId0
==
'P'
&&
frameId1
==
'I'
&&
frameId2
==
'C'
)
:
(
frameId0
==
'A'
&&
frameId1
==
'P'
&&
frameId2
==
'I'
&&
frameId3
==
'C'
))
{
frame
=
decodeApicFrame
(
id3Data
,
frameSize
,
majorVersion
);
}
else
if
(
frameId0
==
'T'
)
{
String
id
=
majorVersion
==
2
?
String
.
format
(
Locale
.
US
,
"%c%c%c"
,
frameId0
,
frameId1
,
frameId2
)
:
String
.
format
(
Locale
.
US
,
"%c%c%c%c"
,
frameId0
,
frameId1
,
frameId2
,
frameId3
);
frame
=
decodeTextInformationFrame
(
id3Data
,
frameSize
,
id
);
}
else
if
(
frameId0
==
'C'
&&
frameId1
==
'O'
&&
frameId2
==
'M'
&&
(
frameId3
==
'M'
||
majorVersion
==
2
))
{
frame
=
decodeCommentFrame
(
id3Data
,
frameSize
);
}
else
{
String
id
=
majorVersion
==
2
?
String
.
format
(
Locale
.
US
,
"%c%c%c"
,
frameId0
,
frameId1
,
frameId2
)
:
String
.
format
(
Locale
.
US
,
"%c%c%c%c"
,
frameId0
,
frameId1
,
frameId2
,
frameId3
);
frame
=
decodeBinaryFrame
(
id3Data
,
frameSize
,
id
);
}
return
frame
;
}
catch
(
UnsupportedEncodingException
e
)
{
Log
.
w
(
TAG
,
"Unsupported character encoding"
);
return
null
;
}
finally
{
id3Data
.
setPosition
(
nextFramePosition
);
}
}
private
static
TxxxFrame
decodeTxxxFrame
(
ParsableByteArray
id3Data
,
int
frameSize
)
...
...
@@ -214,16 +390,29 @@ public final class Id3Decoder implements MetadataDecoder<List<Id3Frame>> {
return
new
GeobFrame
(
mimeType
,
filename
,
description
,
objectData
);
}
private
static
ApicFrame
decodeApicFrame
(
ParsableByteArray
id3Data
,
int
frameSize
)
throws
UnsupportedEncodingException
{
private
static
ApicFrame
decodeApicFrame
(
ParsableByteArray
id3Data
,
int
frameSize
,
int
majorVersion
)
throws
UnsupportedEncodingException
{
int
encoding
=
id3Data
.
readUnsignedByte
();
String
charset
=
getCharsetName
(
encoding
);
byte
[]
data
=
new
byte
[
frameSize
-
1
];
id3Data
.
readBytes
(
data
,
0
,
frameSize
-
1
);
int
mimeTypeEndIndex
=
indexOfZeroByte
(
data
,
0
);
String
mimeType
=
new
String
(
data
,
0
,
mimeTypeEndIndex
,
"ISO-8859-1"
);
String
mimeType
;
int
mimeTypeEndIndex
;
if
(
majorVersion
==
2
)
{
mimeTypeEndIndex
=
2
;
mimeType
=
"image/"
+
new
String
(
data
,
0
,
3
,
"ISO-8859-1"
).
toLowerCase
();
if
(
mimeType
.
equals
(
"image/jpg"
))
{
mimeType
=
"image/jpeg"
;
}
}
else
{
mimeTypeEndIndex
=
indexOfZeroByte
(
data
,
0
);
mimeType
=
new
String
(
data
,
0
,
mimeTypeEndIndex
,
"ISO-8859-1"
).
toLowerCase
();
if
(
mimeType
.
indexOf
(
'/'
)
==
-
1
)
{
mimeType
=
"image/"
+
mimeType
;
}
}
int
pictureType
=
data
[
mimeTypeEndIndex
+
1
]
&
0xFF
;
...
...
@@ -238,6 +427,28 @@ public final class Id3Decoder implements MetadataDecoder<List<Id3Frame>> {
return
new
ApicFrame
(
mimeType
,
description
,
pictureType
,
pictureData
);
}
private
static
CommentFrame
decodeCommentFrame
(
ParsableByteArray
id3Data
,
int
frameSize
)
throws
UnsupportedEncodingException
{
int
encoding
=
id3Data
.
readUnsignedByte
();
String
charset
=
getCharsetName
(
encoding
);
byte
[]
data
=
new
byte
[
3
];
id3Data
.
readBytes
(
data
,
0
,
3
);
String
language
=
new
String
(
data
,
0
,
3
);
data
=
new
byte
[
frameSize
-
4
];
id3Data
.
readBytes
(
data
,
0
,
frameSize
-
4
);
int
descriptionEndIndex
=
indexOfEos
(
data
,
0
,
encoding
);
String
description
=
new
String
(
data
,
0
,
descriptionEndIndex
,
charset
);
int
textStartIndex
=
descriptionEndIndex
+
delimiterLength
(
encoding
);
int
textEndIndex
=
indexOfEos
(
data
,
textStartIndex
,
encoding
);
String
text
=
new
String
(
data
,
textStartIndex
,
textEndIndex
-
textStartIndex
,
charset
);
return
new
CommentFrame
(
language
,
description
,
text
);
}
private
static
TextInformationFrame
decodeTextInformationFrame
(
ParsableByteArray
id3Data
,
int
frameSize
,
String
id
)
throws
UnsupportedEncodingException
{
int
encoding
=
id3Data
.
readUnsignedByte
();
...
...
@@ -261,6 +472,25 @@ public final class Id3Decoder implements MetadataDecoder<List<Id3Frame>> {
}
/**
* Performs in-place removal of unsynchronization for {@code length} bytes starting from
* {@link ParsableByteArray#getPosition()}
*
* @param data Contains the data to be processed.
* @param length The length of the data to be processed.
* @return The length of the data after processing.
*/
private
static
int
removeUnsynchronization
(
ParsableByteArray
data
,
int
length
)
{
byte
[]
bytes
=
data
.
data
;
for
(
int
i
=
data
.
getPosition
();
i
+
1
<
length
;
i
++)
{
if
((
bytes
[
i
]
&
0xFF
)
==
0xFF
&&
bytes
[
i
+
1
]
==
0x00
)
{
System
.
arraycopy
(
bytes
,
i
+
2
,
bytes
,
i
+
1
,
length
-
i
-
2
);
length
--;
}
}
return
length
;
}
/**
* Maps encoding byte from ID3v2 frame to a Charset.
* @param encodingByte The value of encoding byte from ID3v2 frame.
* @return Charset name.
...
...
@@ -280,4 +510,18 @@ public final class Id3Decoder implements MetadataDecoder<List<Id3Frame>> {
}
}
private
static
final
class
Id3Header
{
private
final
int
majorVersion
;
private
final
boolean
isUnsynchronized
;
private
final
int
framesSize
;
public
Id3Header
(
int
majorVersion
,
boolean
isUnsynchronized
,
int
framesSize
)
{
this
.
majorVersion
=
majorVersion
;
this
.
isUnsynchronized
=
isUnsynchronized
;
this
.
framesSize
=
framesSize
;
}
}
}
library/src/main/java/com/google/android/exoplayer2/metadata/id3/Id3Frame.java
View file @
ced03e9a
...
...
@@ -15,10 +15,13 @@
*/
package
com
.
google
.
android
.
exoplayer2
.
metadata
.
id3
;
import
com.google.android.exoplayer2.metadata.Metadata
;
import
com.google.android.exoplayer2.util.Assertions
;
/**
* Base class for ID3 frames.
*/
public
abstract
class
Id3Frame
{
public
abstract
class
Id3Frame
implements
Metadata
.
Entry
{
/**
* The frame ID.
...
...
@@ -26,7 +29,12 @@ public abstract class Id3Frame {
public
final
String
id
;
public
Id3Frame
(
String
id
)
{
this
.
id
=
id
;
this
.
id
=
Assertions
.
checkNotNull
(
id
);
}
@Override
public
int
describeContents
()
{
return
0
;
}
}
library/src/main/java/com/google/android/exoplayer2/metadata/id3/PrivFrame.java
View file @
ced03e9a
...
...
@@ -15,6 +15,11 @@
*/
package
com
.
google
.
android
.
exoplayer2
.
metadata
.
id3
;
import
android.os.Parcel
;
import
android.os.Parcelable
;
import
com.google.android.exoplayer2.util.Util
;
import
java.util.Arrays
;
/**
* PRIV (Private) ID3 frame.
*/
...
...
@@ -31,4 +36,50 @@ public final class PrivFrame extends Id3Frame {
this
.
privateData
=
privateData
;
}
/* package */
PrivFrame
(
Parcel
in
)
{
super
(
ID
);
owner
=
in
.
readString
();
privateData
=
in
.
createByteArray
();
}
@Override
public
boolean
equals
(
Object
obj
)
{
if
(
this
==
obj
)
{
return
true
;
}
if
(
obj
==
null
||
getClass
()
!=
obj
.
getClass
())
{
return
false
;
}
PrivFrame
other
=
(
PrivFrame
)
obj
;
return
Util
.
areEqual
(
owner
,
other
.
owner
)
&&
Arrays
.
equals
(
privateData
,
other
.
privateData
);
}
@Override
public
int
hashCode
()
{
int
result
=
17
;
result
=
31
*
result
+
(
owner
!=
null
?
owner
.
hashCode
()
:
0
);
result
=
31
*
result
+
Arrays
.
hashCode
(
privateData
);
return
result
;
}
@Override
public
void
writeToParcel
(
Parcel
dest
,
int
flags
)
{
dest
.
writeString
(
owner
);
dest
.
writeByteArray
(
privateData
);
}
public
static
final
Parcelable
.
Creator
<
PrivFrame
>
CREATOR
=
new
Parcelable
.
Creator
<
PrivFrame
>()
{
@Override
public
PrivFrame
createFromParcel
(
Parcel
in
)
{
return
new
PrivFrame
(
in
);
}
@Override
public
PrivFrame
[]
newArray
(
int
size
)
{
return
new
PrivFrame
[
size
];
}
};
}
library/src/main/java/com/google/android/exoplayer2/metadata/id3/TextInformationFrame.java
View file @
ced03e9a
...
...
@@ -15,6 +15,10 @@
*/
package
com
.
google
.
android
.
exoplayer2
.
metadata
.
id3
;
import
android.os.Parcel
;
import
android.os.Parcelable
;
import
com.google.android.exoplayer2.util.Util
;
/**
* Text information ("T000" - "TZZZ", excluding "TXXX") ID3 frame.
*/
...
...
@@ -27,4 +31,50 @@ public final class TextInformationFrame extends Id3Frame {
this
.
description
=
description
;
}
/* package */
TextInformationFrame
(
Parcel
in
)
{
super
(
in
.
readString
());
description
=
in
.
readString
();
}
@Override
public
boolean
equals
(
Object
obj
)
{
if
(
this
==
obj
)
{
return
true
;
}
if
(
obj
==
null
||
getClass
()
!=
obj
.
getClass
())
{
return
false
;
}
TextInformationFrame
other
=
(
TextInformationFrame
)
obj
;
return
id
.
equals
(
other
.
id
)
&&
Util
.
areEqual
(
description
,
other
.
description
);
}
@Override
public
int
hashCode
()
{
int
result
=
17
;
result
=
31
*
result
+
id
.
hashCode
();
result
=
31
*
result
+
(
description
!=
null
?
description
.
hashCode
()
:
0
);
return
result
;
}
@Override
public
void
writeToParcel
(
Parcel
dest
,
int
flags
)
{
dest
.
writeString
(
id
);
dest
.
writeString
(
description
);
}
public
static
final
Parcelable
.
Creator
<
TextInformationFrame
>
CREATOR
=
new
Parcelable
.
Creator
<
TextInformationFrame
>()
{
@Override
public
TextInformationFrame
createFromParcel
(
Parcel
in
)
{
return
new
TextInformationFrame
(
in
);
}
@Override
public
TextInformationFrame
[]
newArray
(
int
size
)
{
return
new
TextInformationFrame
[
size
];
}
};
}
library/src/main/java/com/google/android/exoplayer2/metadata/id3/TxxxFrame.java
View file @
ced03e9a
...
...
@@ -15,6 +15,10 @@
*/
package
com
.
google
.
android
.
exoplayer2
.
metadata
.
id3
;
import
android.os.Parcel
;
import
android.os.Parcelable
;
import
com.google.android.exoplayer2.util.Util
;
/**
* TXXX (User defined text information) ID3 frame.
*/
...
...
@@ -31,4 +35,50 @@ public final class TxxxFrame extends Id3Frame {
this
.
value
=
value
;
}
/* package */
TxxxFrame
(
Parcel
in
)
{
super
(
ID
);
description
=
in
.
readString
();
value
=
in
.
readString
();
}
@Override
public
boolean
equals
(
Object
obj
)
{
if
(
this
==
obj
)
{
return
true
;
}
if
(
obj
==
null
||
getClass
()
!=
obj
.
getClass
())
{
return
false
;
}
TxxxFrame
other
=
(
TxxxFrame
)
obj
;
return
Util
.
areEqual
(
description
,
other
.
description
)
&&
Util
.
areEqual
(
value
,
other
.
value
);
}
@Override
public
int
hashCode
()
{
int
result
=
17
;
result
=
31
*
result
+
(
description
!=
null
?
description
.
hashCode
()
:
0
);
result
=
31
*
result
+
(
value
!=
null
?
value
.
hashCode
()
:
0
);
return
result
;
}
@Override
public
void
writeToParcel
(
Parcel
dest
,
int
flags
)
{
dest
.
writeString
(
description
);
dest
.
writeString
(
value
);
}
public
static
final
Parcelable
.
Creator
<
TxxxFrame
>
CREATOR
=
new
Parcelable
.
Creator
<
TxxxFrame
>()
{
@Override
public
TxxxFrame
createFromParcel
(
Parcel
in
)
{
return
new
TxxxFrame
(
in
);
}
@Override
public
TxxxFrame
[]
newArray
(
int
size
)
{
return
new
TxxxFrame
[
size
];
}
};
}
library/src/main/java/com/google/android/exoplayer2/ui/AspectRatioFrameLayout.java
View file @
ced03e9a
...
...
@@ -17,10 +17,13 @@ package com.google.android.exoplayer2.ui;
import
android.content.Context
;
import
android.content.res.TypedArray
;
import
android.support.annotation.IntDef
;
import
android.util.AttributeSet
;
import
android.widget.FrameLayout
;
import
com.google.android.exoplayer2.R
;
import
java.lang.annotation.Retention
;
import
java.lang.annotation.RetentionPolicy
;
/**
* A {@link FrameLayout} that resizes itself to match a specified aspect ratio.
...
...
@@ -28,6 +31,13 @@ import com.google.android.exoplayer2.R;
public
final
class
AspectRatioFrameLayout
extends
FrameLayout
{
/**
* Resize modes for {@link AspectRatioFrameLayout}.
*/
@Retention
(
RetentionPolicy
.
SOURCE
)
@IntDef
({
RESIZE_MODE_FIT
,
RESIZE_MODE_FIXED_WIDTH
,
RESIZE_MODE_FIXED_HEIGHT
})
public
@interface
ResizeMode
{}
/**
* Either the width or height is decreased to obtain the desired aspect ratio.
*/
public
static
final
int
RESIZE_MODE_FIT
=
0
;
...
...
@@ -85,12 +95,11 @@ public final class AspectRatioFrameLayout extends FrameLayout {
}
/**
* Sets the resize mode which can be of value {@link #RESIZE_MODE_FIT},
* {@link #RESIZE_MODE_FIXED_HEIGHT} or {@link #RESIZE_MODE_FIXED_WIDTH}.
* Sets the resize mode.
*
* @param resizeMode The resize mode.
*/
public
void
setResizeMode
(
int
resizeMode
)
{
public
void
setResizeMode
(
@ResizeMode
int
resizeMode
)
{
if
(
this
.
resizeMode
!=
resizeMode
)
{
this
.
resizeMode
=
resizeMode
;
requestLayout
();
...
...
library/src/main/java/com/google/android/exoplayer2/ui/PlaybackControlView.java
View file @
ced03e9a
...
...
@@ -120,7 +120,7 @@ public class PlaybackControlView extends FrameLayout {
public
PlaybackControlView
(
Context
context
,
AttributeSet
attrs
,
int
defStyleAttr
)
{
super
(
context
,
attrs
,
defStyleAttr
);
int
layoutResource
Id
=
R
.
layout
.
exo_playback_control_view
;
int
controllerLayout
Id
=
R
.
layout
.
exo_playback_control_view
;
rewindMs
=
DEFAULT_REWIND_MS
;
fastForwardMs
=
DEFAULT_FAST_FORWARD_MS
;
showTimeoutMs
=
DEFAULT_SHOW_TIMEOUT_MS
;
...
...
@@ -132,8 +132,8 @@ public class PlaybackControlView extends FrameLayout {
fastForwardMs
=
a
.
getInt
(
R
.
styleable
.
PlaybackControlView_fastforward_increment
,
fastForwardMs
);
showTimeoutMs
=
a
.
getInt
(
R
.
styleable
.
PlaybackControlView_show_timeout
,
showTimeoutMs
);
layoutResource
Id
=
a
.
getResourceId
(
R
.
styleable
.
PlaybackControlView_controller_layout_id
,
layoutResource
Id
);
controllerLayout
Id
=
a
.
getResourceId
(
R
.
styleable
.
PlaybackControlView_controller_layout_id
,
controllerLayout
Id
);
}
finally
{
a
.
recycle
();
}
...
...
@@ -143,7 +143,7 @@ public class PlaybackControlView extends FrameLayout {
formatter
=
new
Formatter
(
formatBuilder
,
Locale
.
getDefault
());
componentListener
=
new
ComponentListener
();
LayoutInflater
.
from
(
context
).
inflate
(
layoutResource
Id
,
this
);
LayoutInflater
.
from
(
context
).
inflate
(
controllerLayout
Id
,
this
);
time
=
(
TextView
)
findViewById
(
R
.
id
.
exo_time
);
timeCurrent
=
(
TextView
)
findViewById
(
R
.
id
.
exo_time_current
);
progressBar
=
(
SeekBar
)
findViewById
(
R
.
id
.
exo_progress
);
...
...
library/src/main/java/com/google/android/exoplayer2/ui/SimpleExoPlayerView.java
View file @
ced03e9a
...
...
@@ -18,6 +18,7 @@ package com.google.android.exoplayer2.ui;
import
android.annotation.TargetApi
;
import
android.content.Context
;
import
android.content.res.TypedArray
;
import
android.graphics.BitmapFactory
;
import
android.util.AttributeSet
;
import
android.view.KeyEvent
;
import
android.view.LayoutInflater
;
...
...
@@ -27,16 +28,22 @@ import android.view.TextureView;
import
android.view.View
;
import
android.view.ViewGroup
;
import
android.widget.FrameLayout
;
import
android.widget.ImageView
;
import
com.google.android.exoplayer2.C
;
import
com.google.android.exoplayer2.ExoPlaybackException
;
import
com.google.android.exoplayer2.ExoPlayer
;
import
com.google.android.exoplayer2.R
;
import
com.google.android.exoplayer2.SimpleExoPlayer
;
import
com.google.android.exoplayer2.Timeline
;
import
com.google.android.exoplayer2.metadata.Metadata
;
import
com.google.android.exoplayer2.metadata.id3.ApicFrame
;
import
com.google.android.exoplayer2.source.TrackGroupArray
;
import
com.google.android.exoplayer2.text.Cue
;
import
com.google.android.exoplayer2.text.TextRenderer
;
import
com.google.android.exoplayer2.trackselection.TrackSelection
;
import
com.google.android.exoplayer2.trackselection.TrackSelectionArray
;
import
com.google.android.exoplayer2.ui.AspectRatioFrameLayout.ResizeMode
;
import
com.google.android.exoplayer2.util.Assertions
;
import
java.util.List
;
/**
...
...
@@ -45,15 +52,22 @@ import java.util.List;
@TargetApi
(
16
)
public
final
class
SimpleExoPlayerView
extends
FrameLayout
{
private
final
View
surfaceView
;
private
static
final
int
SURFACE_TYPE_NONE
=
0
;
private
static
final
int
SURFACE_TYPE_SURFACE_VIEW
=
1
;
private
static
final
int
SURFACE_TYPE_TEXTURE_VIEW
=
2
;
private
final
ViewGroup
videoFrame
;
private
final
AspectRatioFrameLayout
aspectRatioVideoFrame
;
private
final
View
shutterView
;
private
final
SubtitleView
subtitleLayout
;
private
final
AspectRatioFrameLayout
layout
;
private
final
View
surfaceView
;
private
final
ImageView
artworkView
;
private
final
SubtitleView
subtitleView
;
private
final
PlaybackControlView
controller
;
private
final
ComponentListener
componentListener
;
private
SimpleExoPlayer
player
;
private
boolean
useController
=
true
;
private
boolean
useController
;
private
boolean
useArtwork
;
private
int
controllerShowTimeoutMs
;
public
SimpleExoPlayerView
(
Context
context
)
{
...
...
@@ -67,7 +81,10 @@ public final class SimpleExoPlayerView extends FrameLayout {
public
SimpleExoPlayerView
(
Context
context
,
AttributeSet
attrs
,
int
defStyleAttr
)
{
super
(
context
,
attrs
,
defStyleAttr
);
boolean
useTextureView
=
false
;
int
playerLayoutId
=
R
.
layout
.
exo_simple_player_view
;
boolean
useArtwork
=
true
;
boolean
useController
=
true
;
int
surfaceType
=
SURFACE_TYPE_SURFACE_VIEW
;
int
resizeMode
=
AspectRatioFrameLayout
.
RESIZE_MODE_FIT
;
int
rewindMs
=
PlaybackControlView
.
DEFAULT_REWIND_MS
;
int
fastForwardMs
=
PlaybackControlView
.
DEFAULT_FAST_FORWARD_MS
;
...
...
@@ -76,11 +93,12 @@ public final class SimpleExoPlayerView extends FrameLayout {
TypedArray
a
=
context
.
getTheme
().
obtainStyledAttributes
(
attrs
,
R
.
styleable
.
SimpleExoPlayerView
,
0
,
0
);
try
{
playerLayoutId
=
a
.
getResourceId
(
R
.
styleable
.
SimpleExoPlayerView_player_layout_id
,
playerLayoutId
);
useArtwork
=
a
.
getBoolean
(
R
.
styleable
.
SimpleExoPlayerView_use_artwork
,
useArtwork
);
useController
=
a
.
getBoolean
(
R
.
styleable
.
SimpleExoPlayerView_use_controller
,
useController
);
useTextureView
=
a
.
getBoolean
(
R
.
styleable
.
SimpleExoPlayerView_use_texture_view
,
useTextureView
);
resizeMode
=
a
.
getInt
(
R
.
styleable
.
SimpleExoPlayerView_resize_mode
,
AspectRatioFrameLayout
.
RESIZE_MODE_FIT
);
surfaceType
=
a
.
getInt
(
R
.
styleable
.
SimpleExoPlayerView_surface_type
,
surfaceType
);
resizeMode
=
a
.
getInt
(
R
.
styleable
.
SimpleExoPlayerView_resize_mode
,
resizeMode
);
rewindMs
=
a
.
getInt
(
R
.
styleable
.
SimpleExoPlayerView_rewind_increment
,
rewindMs
);
fastForwardMs
=
a
.
getInt
(
R
.
styleable
.
SimpleExoPlayerView_fastforward_increment
,
fastForwardMs
);
...
...
@@ -91,35 +109,64 @@ public final class SimpleExoPlayerView extends FrameLayout {
}
}
LayoutInflater
.
from
(
context
).
inflate
(
R
.
layout
.
exo_simple_player_view
,
this
);
LayoutInflater
.
from
(
context
).
inflate
(
playerLayoutId
,
this
);
componentListener
=
new
ComponentListener
();
layout
=
(
AspectRatioFrameLayout
)
findViewById
(
R
.
id
.
exo_video_frame
);
layout
.
setResizeMode
(
resizeMode
);
shutterView
=
findViewById
(
R
.
id
.
exo_shutter
);
subtitleLayout
=
(
SubtitleView
)
findViewById
(
R
.
id
.
exo_subtitles
);
subtitleLayout
.
setUserDefaultStyle
();
subtitleLayout
.
setUserDefaultTextSize
();
View
controllerPlaceholder
=
findViewById
(
R
.
id
.
exo_controller_placeholder
);
videoFrame
=
(
ViewGroup
)
findViewById
(
R
.
id
.
exo_video_frame
);
if
(
videoFrame
!=
null
)
{
if
(
videoFrame
instanceof
AspectRatioFrameLayout
)
{
aspectRatioVideoFrame
=
(
AspectRatioFrameLayout
)
videoFrame
;
setResizeModeRaw
(
aspectRatioVideoFrame
,
resizeMode
);
}
else
{
aspectRatioVideoFrame
=
null
;
}
shutterView
=
Assertions
.
checkNotNull
(
videoFrame
.
findViewById
(
R
.
id
.
exo_shutter
));
if
(
surfaceType
!=
SURFACE_TYPE_NONE
)
{
ViewGroup
.
LayoutParams
params
=
new
ViewGroup
.
LayoutParams
(
ViewGroup
.
LayoutParams
.
MATCH_PARENT
,
ViewGroup
.
LayoutParams
.
MATCH_PARENT
);
surfaceView
=
surfaceType
==
SURFACE_TYPE_TEXTURE_VIEW
?
new
TextureView
(
context
)
:
new
SurfaceView
(
context
);
surfaceView
.
setLayoutParams
(
params
);
videoFrame
.
addView
(
surfaceView
,
0
);
}
else
{
surfaceView
=
null
;
}
}
else
{
aspectRatioVideoFrame
=
null
;
shutterView
=
null
;
surfaceView
=
null
;
}
controller
=
new
PlaybackControlView
(
context
,
attrs
);
artworkView
=
(
ImageView
)
findViewById
(
R
.
id
.
exo_artwork
);
this
.
useArtwork
=
useArtwork
&&
artworkView
!=
null
;
subtitleView
=
(
SubtitleView
)
findViewById
(
R
.
id
.
exo_subtitles
);
if
(
subtitleView
!=
null
)
{
subtitleView
.
setUserDefaultStyle
();
subtitleView
.
setUserDefaultTextSize
();
}
PlaybackControlView
controller
=
(
PlaybackControlView
)
findViewById
(
R
.
id
.
exo_controller
);
if
(
controller
!=
null
)
{
controller
.
setRewindIncrementMs
(
rewindMs
);
controller
.
setFastForwardIncrementMs
(
fastForwardMs
);
}
else
{
View
controllerPlaceholder
=
findViewById
(
R
.
id
.
exo_controller_placeholder
);
if
(
controllerPlaceholder
!=
null
)
{
// Note: rewindMs and fastForwardMs are passed via attrs, so we don't need to make explicit
// calls to set them.
controller
=
new
PlaybackControlView
(
context
,
attrs
);
controller
.
setLayoutParams
(
controllerPlaceholder
.
getLayoutParams
());
controller
.
hide
();
this
.
controllerShowTimeoutMs
=
controllerShowTimeoutMs
;
ViewGroup
parent
=
((
ViewGroup
)
controllerPlaceholder
.
getParent
());
int
controllerIndex
=
parent
.
indexOfChild
(
controllerPlaceholder
);
parent
.
removeView
(
controllerPlaceholder
);
parent
.
addView
(
controller
,
controllerIndex
);
View
view
=
useTextureView
?
new
TextureView
(
context
)
:
new
SurfaceView
(
context
);
ViewGroup
.
LayoutParams
params
=
new
ViewGroup
.
LayoutParams
(
ViewGroup
.
LayoutParams
.
MATCH_PARENT
,
ViewGroup
.
LayoutParams
.
MATCH_PARENT
);
view
.
setLayoutParams
(
params
);
surfaceView
=
view
;
layout
.
addView
(
surfaceView
,
0
);
}
}
this
.
controller
=
controller
;
this
.
controllerShowTimeoutMs
=
controller
!=
null
?
controllerShowTimeoutMs
:
0
;
this
.
useController
=
useController
&&
controller
!=
null
;
hideController
();
}
/**
...
...
@@ -150,6 +197,9 @@ public final class SimpleExoPlayerView extends FrameLayout {
if
(
useController
)
{
controller
.
setPlayer
(
player
);
}
if
(
shutterView
!=
null
)
{
shutterView
.
setVisibility
(
VISIBLE
);
}
if
(
player
!=
null
)
{
if
(
surfaceView
instanceof
TextureView
)
{
player
.
setVideoTextureView
((
TextureView
)
surfaceView
);
...
...
@@ -160,21 +210,41 @@ public final class SimpleExoPlayerView extends FrameLayout {
player
.
addListener
(
componentListener
);
player
.
setTextOutput
(
componentListener
);
maybeShowController
(
false
);
updateForCurrentTrackSelections
();
}
else
{
shutterView
.
setVisibility
(
VISIBLE
);
controller
.
hide
();
hideController
(
);
hideArtwork
();
}
}
/**
* Sets the resize mode which can be of value {@link AspectRatioFrameLayout#RESIZE_MODE_FIT},
* {@link AspectRatioFrameLayout#RESIZE_MODE_FIXED_HEIGHT} or
* {@link AspectRatioFrameLayout#RESIZE_MODE_FIXED_WIDTH}.
* Sets the resize mode.
*
* @param resizeMode The resize mode.
*/
public
void
setResizeMode
(
int
resizeMode
)
{
layout
.
setResizeMode
(
resizeMode
);
public
void
setResizeMode
(
@ResizeMode
int
resizeMode
)
{
Assertions
.
checkState
(
aspectRatioVideoFrame
!=
null
);
aspectRatioVideoFrame
.
setResizeMode
(
resizeMode
);
}
/**
* Returns whether artwork is displayed if present in the media.
*/
public
boolean
getUseArtwork
()
{
return
useArtwork
;
}
/**
* Sets whether artwork is displayed if present in the media.
*
* @param useArtwork Whether artwork is displayed.
*/
public
void
setUseArtwork
(
boolean
useArtwork
)
{
Assertions
.
checkState
(!
useArtwork
||
artworkView
!=
null
);
if
(
this
.
useArtwork
!=
useArtwork
)
{
this
.
useArtwork
=
useArtwork
;
updateForCurrentTrackSelections
();
}
}
/**
...
...
@@ -191,13 +261,14 @@ public final class SimpleExoPlayerView extends FrameLayout {
* @param useController Whether playback controls should be enabled.
*/
public
void
setUseController
(
boolean
useController
)
{
Assertions
.
checkState
(!
useController
||
controller
!=
null
);
if
(
this
.
useController
==
useController
)
{
return
;
}
this
.
useController
=
useController
;
if
(
useController
)
{
controller
.
setPlayer
(
player
);
}
else
{
}
else
if
(
controller
!=
null
)
{
controller
.
hide
();
controller
.
setPlayer
(
null
);
}
...
...
@@ -223,6 +294,7 @@ public final class SimpleExoPlayerView extends FrameLayout {
* the controller to remain visible indefinitely.
*/
public
void
setControllerShowTimeoutMs
(
int
controllerShowTimeoutMs
)
{
Assertions
.
checkState
(
controller
!=
null
);
this
.
controllerShowTimeoutMs
=
controllerShowTimeoutMs
;
}
...
...
@@ -232,6 +304,7 @@ public final class SimpleExoPlayerView extends FrameLayout {
* @param listener The listener to be notified about visibility changes.
*/
public
void
setControllerVisibilityListener
(
PlaybackControlView
.
VisibilityListener
listener
)
{
Assertions
.
checkState
(
controller
!=
null
);
controller
.
setVisibilityListener
(
listener
);
}
...
...
@@ -241,6 +314,7 @@ public final class SimpleExoPlayerView extends FrameLayout {
* @param rewindMs The rewind increment in milliseconds.
*/
public
void
setRewindIncrementMs
(
int
rewindMs
)
{
Assertions
.
checkState
(
controller
!=
null
);
controller
.
setRewindIncrementMs
(
rewindMs
);
}
...
...
@@ -250,6 +324,7 @@ public final class SimpleExoPlayerView extends FrameLayout {
* @param fastForwardMs The fast forward increment in milliseconds.
*/
public
void
setFastForwardIncrementMs
(
int
fastForwardMs
)
{
Assertions
.
checkState
(
controller
!=
null
);
controller
.
setFastForwardIncrementMs
(
fastForwardMs
);
}
...
...
@@ -304,6 +379,67 @@ public final class SimpleExoPlayerView extends FrameLayout {
}
}
private
void
updateForCurrentTrackSelections
()
{
if
(
player
==
null
)
{
return
;
}
TrackSelectionArray
selections
=
player
.
getCurrentTrackSelections
();
for
(
int
i
=
0
;
i
<
selections
.
length
;
i
++)
{
if
(
player
.
getRendererType
(
i
)
==
C
.
TRACK_TYPE_VIDEO
&&
selections
.
get
(
i
)
!=
null
)
{
// Video enabled so artwork must be hidden. If the shutter is closed, it will be opened in
// onRenderedFirstFrame().
hideArtwork
();
return
;
}
}
// Video disabled so the shutter must be closed.
if
(
shutterView
!=
null
)
{
shutterView
.
setVisibility
(
VISIBLE
);
}
// Display artwork if enabled and available, else hide it.
if
(
useArtwork
)
{
for
(
int
i
=
0
;
i
<
selections
.
length
;
i
++)
{
TrackSelection
selection
=
selections
.
get
(
i
);
if
(
selection
!=
null
)
{
for
(
int
j
=
0
;
j
<
selection
.
length
();
j
++)
{
Metadata
metadata
=
selection
.
getFormat
(
j
).
metadata
;
if
(
metadata
!=
null
)
{
for
(
int
k
=
0
;
k
<
metadata
.
length
();
k
++)
{
Metadata
.
Entry
metadataEntry
=
metadata
.
get
(
k
);
if
(
metadataEntry
instanceof
ApicFrame
)
{
byte
[]
data
=
((
ApicFrame
)
metadataEntry
).
pictureData
;;
artworkView
.
setImageBitmap
(
BitmapFactory
.
decodeByteArray
(
data
,
0
,
data
.
length
));
artworkView
.
setVisibility
(
VISIBLE
);
return
;
}
}
}
}
}
}
}
// Artwork disabled or unavailable.
hideArtwork
();
}
private
void
hideArtwork
()
{
if
(
artworkView
!=
null
)
{
artworkView
.
setImageResource
(
android
.
R
.
color
.
transparent
);
// Clears any bitmap reference.
artworkView
.
setVisibility
(
INVISIBLE
);
}
}
private
void
hideController
()
{
if
(
controller
!=
null
)
{
controller
.
hide
();
}
}
@SuppressWarnings
(
"ResourceType"
)
private
static
void
setResizeModeRaw
(
AspectRatioFrameLayout
aspectRatioFrame
,
int
resizeMode
)
{
aspectRatioFrame
.
setResizeMode
(
resizeMode
);
}
private
final
class
ComponentListener
implements
SimpleExoPlayer
.
VideoListener
,
TextRenderer
.
Output
,
ExoPlayer
.
EventListener
{
...
...
@@ -311,7 +447,9 @@ public final class SimpleExoPlayerView extends FrameLayout {
@Override
public
void
onCues
(
List
<
Cue
>
cues
)
{
subtitleLayout
.
onCues
(
cues
);
if
(
subtitleView
!=
null
)
{
subtitleView
.
onCues
(
cues
);
}
}
// SimpleExoPlayer.VideoListener implementation
...
...
@@ -319,23 +457,22 @@ public final class SimpleExoPlayerView extends FrameLayout {
@Override
public
void
onVideoSizeChanged
(
int
width
,
int
height
,
int
unappliedRotationDegrees
,
float
pixelWidthHeightRatio
)
{
layout
.
setAspectRatio
(
height
==
0
?
1
:
(
width
*
pixelWidthHeightRatio
)
/
height
);
if
(
aspectRatioVideoFrame
!=
null
)
{
float
aspectRatio
=
height
==
0
?
1
:
(
width
*
pixelWidthHeightRatio
)
/
height
;
aspectRatioVideoFrame
.
setAspectRatio
(
aspectRatio
);
}
}
@Override
public
void
onRenderedFirstFrame
()
{
shutterView
.
setVisibility
(
GONE
);
if
(
shutterView
!=
null
)
{
shutterView
.
setVisibility
(
INVISIBLE
);
}
}
@Override
public
void
onTracksChanged
(
TrackGroupArray
tracks
,
TrackSelectionArray
selections
)
{
for
(
int
i
=
0
;
i
<
selections
.
length
;
i
++)
{
if
(
player
.
getRendererType
(
i
)
==
C
.
TRACK_TYPE_VIDEO
&&
selections
.
get
(
i
)
!=
null
)
{
return
;
}
}
// No enabled video renderers. Close the shutter.
shutterView
.
setVisibility
(
VISIBLE
);
updateForCurrentTrackSelections
();
}
// ExoPlayer.EventListener implementation
...
...
library/src/main/java/com/google/android/exoplayer2/util/ParsableByteArray.java
View file @
ced03e9a
...
...
@@ -424,6 +424,24 @@ public final class ParsableByteArray {
}
/**
* Reads the next {@code length} bytes as UTF-8 characters. A terminating NUL byte is ignored,
* if present.
*
* @param length The number of bytes to read.
* @return The string encoded by the bytes.
*/
public
String
readNullTerminatedString
(
int
length
)
{
int
stringLength
=
length
;
int
lastIndex
=
position
+
length
-
1
;
if
(
lastIndex
<
limit
&&
data
[
lastIndex
]
==
0
)
{
stringLength
--;
}
String
result
=
new
String
(
data
,
position
,
stringLength
,
Charset
.
defaultCharset
());
position
+=
length
;
return
result
;
}
/**
* Reads the next {@code length} bytes as characters in the specified {@link Charset}.
*
* @param length The number of bytes to read.
...
...
library/src/main/res/layout/exo_playback_control_view.xml
View file @
ced03e9a
...
...
@@ -28,22 +28,22 @@
android:paddingTop=
"4dp"
android:orientation=
"horizontal"
>
<ImageButton
android:id=
"@
+
id/exo_prev"
<ImageButton
android:id=
"@id/exo_prev"
style=
"@style/ExoMediaButton.Previous"
/>
<ImageButton
android:id=
"@
+
id/exo_rew"
<ImageButton
android:id=
"@id/exo_rew"
style=
"@style/ExoMediaButton.Rewind"
/>
<ImageButton
android:id=
"@
+
id/exo_play"
<ImageButton
android:id=
"@id/exo_play"
style=
"@style/ExoMediaButton.Play"
/>
<ImageButton
android:id=
"@
+
id/exo_pause"
<ImageButton
android:id=
"@id/exo_pause"
style=
"@style/ExoMediaButton.Pause"
/>
<ImageButton
android:id=
"@
+
id/exo_ffwd"
<ImageButton
android:id=
"@id/exo_ffwd"
style=
"@style/ExoMediaButton.FastForward"
/>
<ImageButton
android:id=
"@
+
id/exo_next"
<ImageButton
android:id=
"@id/exo_next"
style=
"@style/ExoMediaButton.Next"
/>
</LinearLayout>
...
...
@@ -53,7 +53,7 @@
android:layout_height=
"wrap_content"
android:orientation=
"horizontal"
>
<TextView
android:id=
"@
+
id/exo_time_current"
<TextView
android:id=
"@id/exo_time_current"
android:layout_width=
"wrap_content"
android:layout_height=
"wrap_content"
android:layout_gravity=
"center_horizontal"
...
...
@@ -64,13 +64,13 @@
android:paddingEnd=
"4dp"
android:textColor=
"#FFBEBEBE"
/>
<SeekBar
android:id=
"@
+
id/exo_progress"
<SeekBar
android:id=
"@id/exo_progress"
android:layout_width=
"0dp"
android:layout_weight=
"1"
android:layout_height=
"32dp"
style=
"?android:attr/progressBarStyleHorizontal"
/>
<TextView
android:id=
"@
+
id/exo_time"
<TextView
android:id=
"@id/exo_time"
android:layout_width=
"wrap_content"
android:layout_height=
"wrap_content"
android:layout_gravity=
"center_horizontal"
...
...
library/src/main/res/layout/exo_simple_player_view.xml
View file @
ced03e9a
...
...
@@ -17,23 +17,28 @@
android:layout_height=
"match_parent"
android:layout_width=
"match_parent"
>
<com.google.android.exoplayer2.ui.AspectRatioFrameLayout
android:id=
"@
+
id/exo_video_frame"
<com.google.android.exoplayer2.ui.AspectRatioFrameLayout
android:id=
"@id/exo_video_frame"
android:layout_width=
"match_parent"
android:layout_height=
"match_parent"
android:layout_gravity=
"center"
>
<View
android:id=
"@
+
id/exo_shutter"
<View
android:id=
"@id/exo_shutter"
android:layout_width=
"match_parent"
android:layout_height=
"match_parent"
android:background=
"@android:color/black"
/>
<com.google.android.exoplayer2.ui.SubtitleView
android:id=
"@
+
id/exo_subtitles"
<com.google.android.exoplayer2.ui.SubtitleView
android:id=
"@id/exo_subtitles"
android:layout_width=
"match_parent"
android:layout_height=
"match_parent"
/>
</com.google.android.exoplayer2.ui.AspectRatioFrameLayout>
<View
android:id=
"@+id/exo_controller_placeholder"
<ImageView
android:id=
"@id/exo_artwork"
android:layout_width=
"match_parent"
android:layout_height=
"match_parent"
android:scaleType=
"fitCenter"
/>
<View
android:id=
"@id/exo_controller_placeholder"
android:layout_width=
"match_parent"
android:layout_height=
"match_parent"
/>
...
...
library/src/main/res/values/attrs.xml
View file @
ced03e9a
...
...
@@ -14,24 +14,33 @@
limitations under the License.
-->
<resources>
<!-- Must be kept in sync with AspectRatioFrameLayout -->
<attr
name=
"resize_mode"
format=
"enum"
>
<enum
name=
"fit"
value=
"0"
/>
<enum
name=
"fixed_width"
value=
"1"
/>
<enum
name=
"fixed_height"
value=
"2"
/>
</attr>
<!-- Must be kept in sync with SimpleExoPlayerView -->
<attr
name=
"surface_type"
format=
"enum"
>
<enum
name=
"none"
value=
"0"
/>
<enum
name=
"surface_view"
value=
"1"
/>
<enum
name=
"texture_view"
value=
"2"
/>
</attr>
<attr
name=
"show_timeout"
format=
"integer"
/>
<attr
name=
"rewind_increment"
format=
"integer"
/>
<attr
name=
"fastforward_increment"
format=
"integer"
/>
<attr
name=
"player_layout_id"
format=
"reference"
/>
<attr
name=
"controller_layout_id"
format=
"reference"
/>
<declare-styleable
name=
"SimpleExoPlayerView"
>
<attr
name=
"use_artwork"
format=
"boolean"
/>
<attr
name=
"use_controller"
format=
"boolean"
/>
<attr
name=
"
use_texture_view"
format=
"boolean
"
/>
<attr
name=
"
surface_type
"
/>
<attr
name=
"show_timeout"
/>
<attr
name=
"rewind_increment"
/>
<attr
name=
"fastforward_increment"
/>
<attr
name=
"resize_mode"
/>
<attr
name=
"player_layout_id"
/>
<attr
name=
"controller_layout_id"
/>
</declare-styleable>
...
...
library/src/main/res/values/ids.xml
View file @
ced03e9a
...
...
@@ -14,6 +14,12 @@
limitations under the License.
-->
<resources>
<item
name=
"exo_video_frame"
type=
"id"
/>
<item
name=
"exo_shutter"
type=
"id"
/>
<item
name=
"exo_subtitles"
type=
"id"
/>
<item
name=
"exo_artwork"
type=
"id"
/>
<item
name=
"exo_controller_placeholder"
type=
"id"
/>
<item
name=
"exo_controller"
type=
"id"
/>
<item
name=
"exo_play"
type=
"id"
/>
<item
name=
"exo_pause"
type=
"id"
/>
<item
name=
"exo_rew"
type=
"id"
/>
...
...
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