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
18ab9634
authored
Sep 02, 2016
by
Alan Snyder
Browse files
Options
_('Browse Files')
Download
Email Patches
Plain Diff
Support ID3/Apple metadata parsing in MP3 and MP4 files
parent
c54169c1
Hide whitespace changes
Inline
Side-by-side
Showing
23 changed files
with
1385 additions
and
436 deletions
demo/src/main/java/com/google/android/exoplayer2/demo/EventLogger.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/GaplessInfo.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/AtomParsers.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/MetadataBuilder.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/upstream/ContentDataSource.java
library/src/main/java/com/google/android/exoplayer2/util/ParsableByteArray.java
demo/src/main/java/com/google/android/exoplayer2/demo/EventLogger.java
View file @
18ab9634
...
@@ -27,8 +27,11 @@ import com.google.android.exoplayer2.Timeline;
...
@@ -27,8 +27,11 @@ import com.google.android.exoplayer2.Timeline;
import
com.google.android.exoplayer2.audio.AudioRendererEventListener
;
import
com.google.android.exoplayer2.audio.AudioRendererEventListener
;
import
com.google.android.exoplayer2.decoder.DecoderCounters
;
import
com.google.android.exoplayer2.decoder.DecoderCounters
;
import
com.google.android.exoplayer2.drm.StreamingDrmSessionManager
;
import
com.google.android.exoplayer2.drm.StreamingDrmSessionManager
;
import
com.google.android.exoplayer2.extractor.GaplessInfo
;
import
com.google.android.exoplayer2.metadata.Metadata
;
import
com.google.android.exoplayer2.metadata.MetadataRenderer
;
import
com.google.android.exoplayer2.metadata.MetadataRenderer
;
import
com.google.android.exoplayer2.metadata.id3.ApicFrame
;
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.GeobFrame
;
import
com.google.android.exoplayer2.metadata.id3.Id3Frame
;
import
com.google.android.exoplayer2.metadata.id3.Id3Frame
;
import
com.google.android.exoplayer2.metadata.id3.PrivFrame
;
import
com.google.android.exoplayer2.metadata.id3.PrivFrame
;
...
@@ -54,7 +57,7 @@ import java.util.Locale;
...
@@ -54,7 +57,7 @@ import java.util.Locale;
/* package */
final
class
EventLogger
implements
ExoPlayer
.
EventListener
,
/* package */
final
class
EventLogger
implements
ExoPlayer
.
EventListener
,
AudioRendererEventListener
,
VideoRendererEventListener
,
AdaptiveMediaSourceEventListener
,
AudioRendererEventListener
,
VideoRendererEventListener
,
AdaptiveMediaSourceEventListener
,
ExtractorMediaSource
.
EventListener
,
StreamingDrmSessionManager
.
EventListener
,
ExtractorMediaSource
.
EventListener
,
StreamingDrmSessionManager
.
EventListener
,
MappingTrackSelector
.
EventListener
,
MetadataRenderer
.
Output
<
List
<
Id3Frame
>
>
{
MappingTrackSelector
.
EventListener
,
MetadataRenderer
.
Output
<
Metadata
>
{
private
static
final
String
TAG
=
"EventLogger"
;
private
static
final
String
TAG
=
"EventLogger"
;
private
static
final
int
MAX_TIMELINE_ITEM_LINES
=
3
;
private
static
final
int
MAX_TIMELINE_ITEM_LINES
=
3
;
...
@@ -173,10 +176,11 @@ import java.util.Locale;
...
@@ -173,10 +176,11 @@ import java.util.Locale;
Log
.
d
(
TAG
,
"]"
);
Log
.
d
(
TAG
,
"]"
);
}
}
// MetadataRenderer.Output<
List<Id3Frame>
>
// MetadataRenderer.Output<
Metadata
>
@Override
@Override
public
void
onMetadata
(
List
<
Id3Frame
>
id3Frames
)
{
public
void
onMetadata
(
Metadata
metadata
)
{
List
<
Id3Frame
>
id3Frames
=
metadata
.
getFrames
();
for
(
Id3Frame
id3Frame
:
id3Frames
)
{
for
(
Id3Frame
id3Frame
:
id3Frames
)
{
if
(
id3Frame
instanceof
TxxxFrame
)
{
if
(
id3Frame
instanceof
TxxxFrame
)
{
TxxxFrame
txxxFrame
=
(
TxxxFrame
)
id3Frame
;
TxxxFrame
txxxFrame
=
(
TxxxFrame
)
id3Frame
;
...
@@ -197,10 +201,19 @@ import java.util.Locale;
...
@@ -197,10 +201,19 @@ import java.util.Locale;
TextInformationFrame
textInformationFrame
=
(
TextInformationFrame
)
id3Frame
;
TextInformationFrame
textInformationFrame
=
(
TextInformationFrame
)
id3Frame
;
Log
.
i
(
TAG
,
String
.
format
(
"ID3 TimedMetadata %s: description=%s"
,
textInformationFrame
.
id
,
Log
.
i
(
TAG
,
String
.
format
(
"ID3 TimedMetadata %s: description=%s"
,
textInformationFrame
.
id
,
textInformationFrame
.
description
));
textInformationFrame
.
description
));
}
else
if
(
id3Frame
instanceof
CommentFrame
)
{
CommentFrame
commentFrame
=
(
CommentFrame
)
id3Frame
;
Log
.
i
(
TAG
,
String
.
format
(
"ID3 TimedMetadata %s: language=%s text=%s"
,
commentFrame
.
id
,
commentFrame
.
language
,
commentFrame
.
text
));
}
else
{
}
else
{
Log
.
i
(
TAG
,
String
.
format
(
"ID3 TimedMetadata %s"
,
id3Frame
.
id
));
Log
.
i
(
TAG
,
String
.
format
(
"ID3 TimedMetadata %s"
,
id3Frame
.
id
));
}
}
}
}
GaplessInfo
gaplessInfo
=
metadata
.
getGaplessInfo
();
if
(
gaplessInfo
!=
null
)
{
Log
.
i
(
TAG
,
String
.
format
(
"ID3 TimedMetadata encoder delay=%d padding=%d"
,
gaplessInfo
.
encoderDelay
,
gaplessInfo
.
encoderPadding
));
}
}
}
// AudioRendererEventListener
// AudioRendererEventListener
...
...
library/src/androidTest/java/com/google/android/exoplayer2/metadata/id3/Id3DecoderTest.java
View file @
18ab9634
...
@@ -17,6 +17,7 @@ package com.google.android.exoplayer2.metadata.id3;
...
@@ -17,6 +17,7 @@ package com.google.android.exoplayer2.metadata.id3;
import
android.test.MoreAsserts
;
import
android.test.MoreAsserts
;
import
com.google.android.exoplayer2.metadata.MetadataDecoderException
;
import
com.google.android.exoplayer2.metadata.MetadataDecoderException
;
import
com.google.android.exoplayer2.metadata.Metadata
;
import
java.util.List
;
import
java.util.List
;
import
junit.framework.TestCase
;
import
junit.framework.TestCase
;
...
@@ -30,7 +31,8 @@ public class Id3DecoderTest extends TestCase {
...
@@ -30,7 +31,8 @@ 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
,
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
};
54
,
52
,
95
,
115
,
116
,
97
,
114
,
116
,
0
};
Id3Decoder
decoder
=
new
Id3Decoder
();
Id3Decoder
decoder
=
new
Id3Decoder
();
List
<
Id3Frame
>
id3Frames
=
decoder
.
decode
(
rawId3
,
rawId3
.
length
);
Metadata
metadata
=
decoder
.
decode
(
rawId3
,
rawId3
.
length
);
List
<
Id3Frame
>
id3Frames
=
metadata
.
getFrames
();
assertEquals
(
1
,
id3Frames
.
size
());
assertEquals
(
1
,
id3Frames
.
size
());
TxxxFrame
txxxFrame
=
(
TxxxFrame
)
id3Frames
.
get
(
0
);
TxxxFrame
txxxFrame
=
(
TxxxFrame
)
id3Frames
.
get
(
0
);
assertEquals
(
""
,
txxxFrame
.
description
);
assertEquals
(
""
,
txxxFrame
.
description
);
...
@@ -42,7 +44,8 @@ public class Id3DecoderTest extends TestCase {
...
@@ -42,7 +44,8 @@ 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
,
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
};
111
,
114
,
108
,
100
,
0
,
1
,
2
,
3
,
4
,
5
,
6
,
7
,
8
,
9
,
0
};
Id3Decoder
decoder
=
new
Id3Decoder
();
Id3Decoder
decoder
=
new
Id3Decoder
();
List
<
Id3Frame
>
id3Frames
=
decoder
.
decode
(
rawId3
,
rawId3
.
length
);
Metadata
metadata
=
decoder
.
decode
(
rawId3
,
rawId3
.
length
);
List
<
Id3Frame
>
id3Frames
=
metadata
.
getFrames
();
assertEquals
(
1
,
id3Frames
.
size
());
assertEquals
(
1
,
id3Frames
.
size
());
ApicFrame
apicFrame
=
(
ApicFrame
)
id3Frames
.
get
(
0
);
ApicFrame
apicFrame
=
(
ApicFrame
)
id3Frames
.
get
(
0
);
assertEquals
(
"image/jpeg"
,
apicFrame
.
mimeType
);
assertEquals
(
"image/jpeg"
,
apicFrame
.
mimeType
);
...
@@ -56,7 +59,8 @@ public class Id3DecoderTest extends TestCase {
...
@@ -56,7 +59,8 @@ 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
,
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
};
3
,
72
,
101
,
108
,
108
,
111
,
32
,
87
,
111
,
114
,
108
,
100
,
0
};
Id3Decoder
decoder
=
new
Id3Decoder
();
Id3Decoder
decoder
=
new
Id3Decoder
();
List
<
Id3Frame
>
id3Frames
=
decoder
.
decode
(
rawId3
,
rawId3
.
length
);
Metadata
metadata
=
decoder
.
decode
(
rawId3
,
rawId3
.
length
);
List
<
Id3Frame
>
id3Frames
=
metadata
.
getFrames
();
assertEquals
(
1
,
id3Frames
.
size
());
assertEquals
(
1
,
id3Frames
.
size
());
TextInformationFrame
textInformationFrame
=
(
TextInformationFrame
)
id3Frames
.
get
(
0
);
TextInformationFrame
textInformationFrame
=
(
TextInformationFrame
)
id3Frames
.
get
(
0
);
assertEquals
(
"TIT2"
,
textInformationFrame
.
id
);
assertEquals
(
"TIT2"
,
textInformationFrame
.
id
);
...
...
library/src/main/java/com/google/android/exoplayer2/Format.java
View file @
18ab9634
...
@@ -21,6 +21,8 @@ import android.media.MediaFormat;
...
@@ -21,6 +21,8 @@ import android.media.MediaFormat;
import
android.os.Parcel
;
import
android.os.Parcel
;
import
android.os.Parcelable
;
import
android.os.Parcelable
;
import
com.google.android.exoplayer2.drm.DrmInitData
;
import
com.google.android.exoplayer2.drm.DrmInitData
;
import
com.google.android.exoplayer2.extractor.GaplessInfo
;
import
com.google.android.exoplayer2.metadata.Metadata
;
import
com.google.android.exoplayer2.util.MimeTypes
;
import
com.google.android.exoplayer2.util.MimeTypes
;
import
com.google.android.exoplayer2.util.Util
;
import
com.google.android.exoplayer2.util.Util
;
import
java.nio.ByteBuffer
;
import
java.nio.ByteBuffer
;
...
@@ -100,6 +102,11 @@ public final class Format implements Parcelable {
...
@@ -100,6 +102,11 @@ public final class Format implements Parcelable {
* DRM initialization data if the stream is protected, or null otherwise.
* DRM initialization data if the stream is protected, or null otherwise.
*/
*/
public
final
DrmInitData
drmInitData
;
public
final
DrmInitData
drmInitData
;
/**
* Static metadata
*/
public
final
Metadata
metadata
;
// Video specific.
// Video specific.
...
@@ -196,7 +203,7 @@ public final class Format implements Parcelable {
...
@@ -196,7 +203,7 @@ public final class Format implements Parcelable {
float
frameRate
,
List
<
byte
[]>
initializationData
)
{
float
frameRate
,
List
<
byte
[]>
initializationData
)
{
return
new
Format
(
id
,
containerMimeType
,
sampleMimeType
,
codecs
,
bitrate
,
NO_VALUE
,
width
,
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
,
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
,
public
static
Format
createVideoSampleFormat
(
String
id
,
String
sampleMimeType
,
String
codecs
,
...
@@ -222,7 +229,7 @@ public final class Format implements Parcelable {
...
@@ -222,7 +229,7 @@ public final class Format implements Parcelable {
return
new
Format
(
id
,
null
,
sampleMimeType
,
codecs
,
bitrate
,
maxInputSize
,
width
,
height
,
return
new
Format
(
id
,
null
,
sampleMimeType
,
codecs
,
bitrate
,
maxInputSize
,
width
,
height
,
frameRate
,
rotationDegrees
,
pixelWidthHeightRatio
,
projectionData
,
stereoMode
,
NO_VALUE
,
frameRate
,
rotationDegrees
,
pixelWidthHeightRatio
,
projectionData
,
stereoMode
,
NO_VALUE
,
NO_VALUE
,
NO_VALUE
,
NO_VALUE
,
NO_VALUE
,
0
,
null
,
OFFSET_SAMPLE_RELATIVE
,
initializationData
,
NO_VALUE
,
NO_VALUE
,
NO_VALUE
,
NO_VALUE
,
0
,
null
,
OFFSET_SAMPLE_RELATIVE
,
initializationData
,
drmInitData
);
drmInitData
,
null
);
}
}
// Audio.
// Audio.
...
@@ -233,7 +240,7 @@ public final class Format implements Parcelable {
...
@@ -233,7 +240,7 @@ public final class Format implements Parcelable {
return
new
Format
(
id
,
containerMimeType
,
sampleMimeType
,
codecs
,
bitrate
,
NO_VALUE
,
NO_VALUE
,
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
,
NO_VALUE
,
NO_VALUE
,
null
,
NO_VALUE
,
channelCount
,
sampleRate
,
NO_VALUE
,
NO_VALUE
,
NO_VALUE
,
selectionFlags
,
language
,
OFFSET_SAMPLE_RELATIVE
,
initializationData
,
NO_VALUE
,
NO_VALUE
,
selectionFlags
,
language
,
OFFSET_SAMPLE_RELATIVE
,
initializationData
,
null
);
null
,
null
);
}
}
public
static
Format
createAudioSampleFormat
(
String
id
,
String
sampleMimeType
,
String
codecs
,
public
static
Format
createAudioSampleFormat
(
String
id
,
String
sampleMimeType
,
String
codecs
,
...
@@ -260,7 +267,7 @@ public final class Format implements Parcelable {
...
@@ -260,7 +267,7 @@ public final class Format implements Parcelable {
return
new
Format
(
id
,
null
,
sampleMimeType
,
codecs
,
bitrate
,
maxInputSize
,
NO_VALUE
,
NO_VALUE
,
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
,
NO_VALUE
,
NO_VALUE
,
NO_VALUE
,
null
,
NO_VALUE
,
channelCount
,
sampleRate
,
pcmEncoding
,
encoderDelay
,
encoderPadding
,
selectionFlags
,
language
,
OFFSET_SAMPLE_RELATIVE
,
encoderDelay
,
encoderPadding
,
selectionFlags
,
language
,
OFFSET_SAMPLE_RELATIVE
,
initializationData
,
drmInitData
);
initializationData
,
drmInitData
,
null
);
}
}
// Text.
// Text.
...
@@ -269,7 +276,7 @@ public final class Format implements Parcelable {
...
@@ -269,7 +276,7 @@ public final class Format implements Parcelable {
String
sampleMimeType
,
String
codecs
,
int
bitrate
,
int
selectionFlags
,
String
language
)
{
String
sampleMimeType
,
String
codecs
,
int
bitrate
,
int
selectionFlags
,
String
language
)
{
return
new
Format
(
id
,
containerMimeType
,
sampleMimeType
,
codecs
,
bitrate
,
NO_VALUE
,
NO_VALUE
,
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
,
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
,
public
static
Format
createTextSampleFormat
(
String
id
,
String
sampleMimeType
,
String
codecs
,
...
@@ -283,7 +290,7 @@ public final class Format implements Parcelable {
...
@@ -283,7 +290,7 @@ public final class Format implements Parcelable {
long
subsampleOffsetUs
)
{
long
subsampleOffsetUs
)
{
return
new
Format
(
id
,
null
,
sampleMimeType
,
codecs
,
bitrate
,
NO_VALUE
,
NO_VALUE
,
NO_VALUE
,
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
,
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.
// Image.
...
@@ -292,7 +299,7 @@ public final class Format implements Parcelable {
...
@@ -292,7 +299,7 @@ public final class Format implements Parcelable {
int
bitrate
,
List
<
byte
[]>
initializationData
,
String
language
,
DrmInitData
drmInitData
)
{
int
bitrate
,
List
<
byte
[]>
initializationData
,
String
language
,
DrmInitData
drmInitData
)
{
return
new
Format
(
id
,
null
,
sampleMimeType
,
codecs
,
bitrate
,
NO_VALUE
,
NO_VALUE
,
NO_VALUE
,
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
,
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.
// Generic.
...
@@ -301,14 +308,14 @@ public final class Format implements Parcelable {
...
@@ -301,14 +308,14 @@ public final class Format implements Parcelable {
String
sampleMimeType
,
int
bitrate
)
{
String
sampleMimeType
,
int
bitrate
)
{
return
new
Format
(
id
,
containerMimeType
,
sampleMimeType
,
codecs
,
bitrate
,
NO_VALUE
,
NO_VALUE
,
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
,
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
,
public
static
Format
createSampleFormat
(
String
id
,
String
sampleMimeType
,
String
codecs
,
int
bitrate
,
DrmInitData
drmInitData
)
{
int
bitrate
,
DrmInitData
drmInitData
)
{
return
new
Format
(
id
,
null
,
sampleMimeType
,
codecs
,
bitrate
,
NO_VALUE
,
NO_VALUE
,
NO_VALUE
,
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
,
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
,
/* package */
Format
(
String
id
,
String
containerMimeType
,
String
sampleMimeType
,
String
codecs
,
...
@@ -316,7 +323,7 @@ public final class Format implements Parcelable {
...
@@ -316,7 +323,7 @@ public final class Format implements Parcelable {
float
pixelWidthHeightRatio
,
byte
[]
projectionData
,
int
stereoMode
,
int
channelCount
,
float
pixelWidthHeightRatio
,
byte
[]
projectionData
,
int
stereoMode
,
int
channelCount
,
int
sampleRate
,
int
pcmEncoding
,
int
encoderDelay
,
int
encoderPadding
,
int
selectionFlags
,
int
sampleRate
,
int
pcmEncoding
,
int
encoderDelay
,
int
encoderPadding
,
int
selectionFlags
,
String
language
,
long
subsampleOffsetUs
,
List
<
byte
[]>
initializationData
,
String
language
,
long
subsampleOffsetUs
,
List
<
byte
[]>
initializationData
,
DrmInitData
drmInitData
)
{
DrmInitData
drmInitData
,
Metadata
metadata
)
{
this
.
id
=
id
;
this
.
id
=
id
;
this
.
containerMimeType
=
containerMimeType
;
this
.
containerMimeType
=
containerMimeType
;
this
.
sampleMimeType
=
sampleMimeType
;
this
.
sampleMimeType
=
sampleMimeType
;
...
@@ -341,6 +348,7 @@ public final class Format implements Parcelable {
...
@@ -341,6 +348,7 @@ public final class Format implements Parcelable {
this
.
initializationData
=
initializationData
==
null
?
Collections
.<
byte
[]>
emptyList
()
this
.
initializationData
=
initializationData
==
null
?
Collections
.<
byte
[]>
emptyList
()
:
initializationData
;
:
initializationData
;
this
.
drmInitData
=
drmInitData
;
this
.
drmInitData
=
drmInitData
;
this
.
metadata
=
metadata
;
}
}
/* package */
Format
(
Parcel
in
)
{
/* package */
Format
(
Parcel
in
)
{
...
@@ -372,20 +380,21 @@ public final class Format implements Parcelable {
...
@@ -372,20 +380,21 @@ public final class Format implements Parcelable {
initializationData
.
add
(
in
.
createByteArray
());
initializationData
.
add
(
in
.
createByteArray
());
}
}
drmInitData
=
in
.
readParcelable
(
DrmInitData
.
class
.
getClassLoader
());
drmInitData
=
in
.
readParcelable
(
DrmInitData
.
class
.
getClassLoader
());
metadata
=
in
.
readParcelable
(
Metadata
.
class
.
getClassLoader
());
}
}
public
Format
copyWithMaxInputSize
(
int
maxInputSize
)
{
public
Format
copyWithMaxInputSize
(
int
maxInputSize
)
{
return
new
Format
(
id
,
containerMimeType
,
sampleMimeType
,
codecs
,
bitrate
,
maxInputSize
,
return
new
Format
(
id
,
containerMimeType
,
sampleMimeType
,
codecs
,
bitrate
,
maxInputSize
,
width
,
height
,
frameRate
,
rotationDegrees
,
pixelWidthHeightRatio
,
projectionData
,
width
,
height
,
frameRate
,
rotationDegrees
,
pixelWidthHeightRatio
,
projectionData
,
stereoMode
,
channelCount
,
sampleRate
,
pcmEncoding
,
encoderDelay
,
encoderPadding
,
stereoMode
,
channelCount
,
sampleRate
,
pcmEncoding
,
encoderDelay
,
encoderPadding
,
selectionFlags
,
language
,
subsampleOffsetUs
,
initializationData
,
drmInitData
);
selectionFlags
,
language
,
subsampleOffsetUs
,
initializationData
,
drmInitData
,
metadata
);
}
}
public
Format
copyWithSubsampleOffsetUs
(
long
subsampleOffsetUs
)
{
public
Format
copyWithSubsampleOffsetUs
(
long
subsampleOffsetUs
)
{
return
new
Format
(
id
,
containerMimeType
,
sampleMimeType
,
codecs
,
bitrate
,
maxInputSize
,
return
new
Format
(
id
,
containerMimeType
,
sampleMimeType
,
codecs
,
bitrate
,
maxInputSize
,
width
,
height
,
frameRate
,
rotationDegrees
,
pixelWidthHeightRatio
,
projectionData
,
width
,
height
,
frameRate
,
rotationDegrees
,
pixelWidthHeightRatio
,
projectionData
,
stereoMode
,
channelCount
,
sampleRate
,
pcmEncoding
,
encoderDelay
,
encoderPadding
,
stereoMode
,
channelCount
,
sampleRate
,
pcmEncoding
,
encoderDelay
,
encoderPadding
,
selectionFlags
,
language
,
subsampleOffsetUs
,
initializationData
,
drmInitData
);
selectionFlags
,
language
,
subsampleOffsetUs
,
initializationData
,
drmInitData
,
metadata
);
}
}
public
Format
copyWithContainerInfo
(
String
id
,
int
bitrate
,
int
width
,
int
height
,
public
Format
copyWithContainerInfo
(
String
id
,
int
bitrate
,
int
width
,
int
height
,
...
@@ -393,7 +402,7 @@ public final class Format implements Parcelable {
...
@@ -393,7 +402,7 @@ public final class Format implements Parcelable {
return
new
Format
(
id
,
containerMimeType
,
sampleMimeType
,
codecs
,
bitrate
,
maxInputSize
,
return
new
Format
(
id
,
containerMimeType
,
sampleMimeType
,
codecs
,
bitrate
,
maxInputSize
,
width
,
height
,
frameRate
,
rotationDegrees
,
pixelWidthHeightRatio
,
projectionData
,
width
,
height
,
frameRate
,
rotationDegrees
,
pixelWidthHeightRatio
,
projectionData
,
stereoMode
,
channelCount
,
sampleRate
,
pcmEncoding
,
encoderDelay
,
encoderPadding
,
stereoMode
,
channelCount
,
sampleRate
,
pcmEncoding
,
encoderDelay
,
encoderPadding
,
selectionFlags
,
language
,
subsampleOffsetUs
,
initializationData
,
drmInitData
);
selectionFlags
,
language
,
subsampleOffsetUs
,
initializationData
,
drmInitData
,
metadata
);
}
}
public
Format
copyWithManifestFormatInfo
(
Format
manifestFormat
,
public
Format
copyWithManifestFormatInfo
(
Format
manifestFormat
,
...
@@ -409,21 +418,32 @@ public final class Format implements Parcelable {
...
@@ -409,21 +418,32 @@ public final class Format implements Parcelable {
return
new
Format
(
id
,
containerMimeType
,
sampleMimeType
,
codecs
,
bitrate
,
maxInputSize
,
width
,
return
new
Format
(
id
,
containerMimeType
,
sampleMimeType
,
codecs
,
bitrate
,
maxInputSize
,
width
,
height
,
frameRate
,
rotationDegrees
,
pixelWidthHeightRatio
,
projectionData
,
stereoMode
,
height
,
frameRate
,
rotationDegrees
,
pixelWidthHeightRatio
,
projectionData
,
stereoMode
,
channelCount
,
sampleRate
,
pcmEncoding
,
encoderDelay
,
encoderPadding
,
selectionFlags
,
channelCount
,
sampleRate
,
pcmEncoding
,
encoderDelay
,
encoderPadding
,
selectionFlags
,
language
,
subsampleOffsetUs
,
initializationData
,
drmInitData
);
language
,
subsampleOffsetUs
,
initializationData
,
drmInitData
,
null
);
}
}
public
Format
copyWithGaplessInfo
(
int
encoderDelay
,
int
encoderPadding
)
{
public
Format
copyWithGaplessInfo
(
int
encoderDelay
,
int
encoderPadding
)
{
return
new
Format
(
id
,
containerMimeType
,
sampleMimeType
,
codecs
,
bitrate
,
maxInputSize
,
return
new
Format
(
id
,
containerMimeType
,
sampleMimeType
,
codecs
,
bitrate
,
maxInputSize
,
width
,
height
,
frameRate
,
rotationDegrees
,
pixelWidthHeightRatio
,
projectionData
,
width
,
height
,
frameRate
,
rotationDegrees
,
pixelWidthHeightRatio
,
projectionData
,
stereoMode
,
channelCount
,
sampleRate
,
pcmEncoding
,
encoderDelay
,
encoderPadding
,
stereoMode
,
channelCount
,
sampleRate
,
pcmEncoding
,
encoderDelay
,
encoderPadding
,
selectionFlags
,
language
,
subsampleOffsetUs
,
initializationData
,
drmInitData
);
selectionFlags
,
language
,
subsampleOffsetUs
,
initializationData
,
drmInitData
,
metadata
);
}
}
public
Format
copyWithDrmInitData
(
DrmInitData
drmInitData
)
{
public
Format
copyWithDrmInitData
(
DrmInitData
drmInitData
)
{
return
new
Format
(
id
,
containerMimeType
,
sampleMimeType
,
codecs
,
bitrate
,
maxInputSize
,
return
new
Format
(
id
,
containerMimeType
,
sampleMimeType
,
codecs
,
bitrate
,
maxInputSize
,
width
,
height
,
frameRate
,
rotationDegrees
,
pixelWidthHeightRatio
,
projectionData
,
width
,
height
,
frameRate
,
rotationDegrees
,
pixelWidthHeightRatio
,
projectionData
,
stereoMode
,
channelCount
,
sampleRate
,
pcmEncoding
,
encoderDelay
,
encoderPadding
,
stereoMode
,
channelCount
,
sampleRate
,
pcmEncoding
,
encoderDelay
,
encoderPadding
,
selectionFlags
,
language
,
subsampleOffsetUs
,
initializationData
,
drmInitData
);
selectionFlags
,
language
,
subsampleOffsetUs
,
initializationData
,
drmInitData
,
metadata
);
}
public
Format
copyWithMetadata
(
Metadata
metadata
)
{
GaplessInfo
gaplessInfo
=
metadata
.
getGaplessInfo
();
int
ed
=
gaplessInfo
!=
null
?
gaplessInfo
.
encoderDelay
:
encoderDelay
;
int
ep
=
gaplessInfo
!=
null
?
gaplessInfo
.
encoderPadding
:
encoderPadding
;
return
new
Format
(
id
,
containerMimeType
,
sampleMimeType
,
codecs
,
bitrate
,
maxInputSize
,
width
,
height
,
frameRate
,
rotationDegrees
,
pixelWidthHeightRatio
,
projectionData
,
stereoMode
,
channelCount
,
sampleRate
,
pcmEncoding
,
ed
,
ep
,
selectionFlags
,
language
,
subsampleOffsetUs
,
initializationData
,
drmInitData
,
metadata
);
}
}
/**
/**
...
@@ -483,6 +503,7 @@ public final class Format implements Parcelable {
...
@@ -483,6 +503,7 @@ public final class Format implements Parcelable {
result
=
31
*
result
+
sampleRate
;
result
=
31
*
result
+
sampleRate
;
result
=
31
*
result
+
(
language
==
null
?
0
:
language
.
hashCode
());
result
=
31
*
result
+
(
language
==
null
?
0
:
language
.
hashCode
());
result
=
31
*
result
+
(
drmInitData
==
null
?
0
:
drmInitData
.
hashCode
());
result
=
31
*
result
+
(
drmInitData
==
null
?
0
:
drmInitData
.
hashCode
());
result
=
31
*
result
+
(
metadata
==
null
?
0
:
metadata
.
hashCode
());
hashCode
=
result
;
hashCode
=
result
;
}
}
return
hashCode
;
return
hashCode
;
...
@@ -510,6 +531,7 @@ public final class Format implements Parcelable {
...
@@ -510,6 +531,7 @@ public final class Format implements Parcelable {
||
!
Util
.
areEqual
(
sampleMimeType
,
other
.
sampleMimeType
)
||
!
Util
.
areEqual
(
sampleMimeType
,
other
.
sampleMimeType
)
||
!
Util
.
areEqual
(
codecs
,
other
.
codecs
)
||
!
Util
.
areEqual
(
codecs
,
other
.
codecs
)
||
!
Util
.
areEqual
(
drmInitData
,
other
.
drmInitData
)
||
!
Util
.
areEqual
(
drmInitData
,
other
.
drmInitData
)
||
!
Util
.
areEqual
(
metadata
,
other
.
metadata
)
||
!
Arrays
.
equals
(
projectionData
,
other
.
projectionData
)
||
!
Arrays
.
equals
(
projectionData
,
other
.
projectionData
)
||
initializationData
.
size
()
!=
other
.
initializationData
.
size
())
{
||
initializationData
.
size
()
!=
other
.
initializationData
.
size
())
{
return
false
;
return
false
;
...
@@ -582,6 +604,7 @@ public final class Format implements Parcelable {
...
@@ -582,6 +604,7 @@ public final class Format implements Parcelable {
dest
.
writeByteArray
(
initializationData
.
get
(
i
));
dest
.
writeByteArray
(
initializationData
.
get
(
i
));
}
}
dest
.
writeParcelable
(
drmInitData
,
0
);
dest
.
writeParcelable
(
drmInitData
,
0
);
dest
.
writeParcelable
(
metadata
,
0
);
}
}
/**
/**
...
...
library/src/main/java/com/google/android/exoplayer2/SimpleExoPlayer.java
View file @
18ab9634
...
@@ -31,9 +31,9 @@ import com.google.android.exoplayer2.audio.MediaCodecAudioRenderer;
...
@@ -31,9 +31,9 @@ import com.google.android.exoplayer2.audio.MediaCodecAudioRenderer;
import
com.google.android.exoplayer2.decoder.DecoderCounters
;
import
com.google.android.exoplayer2.decoder.DecoderCounters
;
import
com.google.android.exoplayer2.drm.DrmSessionManager
;
import
com.google.android.exoplayer2.drm.DrmSessionManager
;
import
com.google.android.exoplayer2.mediacodec.MediaCodecSelector
;
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.MetadataRenderer
;
import
com.google.android.exoplayer2.metadata.id3.Id3Decoder
;
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.MediaSource
;
import
com.google.android.exoplayer2.text.Cue
;
import
com.google.android.exoplayer2.text.Cue
;
import
com.google.android.exoplayer2.text.TextRenderer
;
import
com.google.android.exoplayer2.text.TextRenderer
;
...
@@ -100,7 +100,7 @@ public final class SimpleExoPlayer implements ExoPlayer {
...
@@ -100,7 +100,7 @@ public final class SimpleExoPlayer implements ExoPlayer {
private
SurfaceHolder
surfaceHolder
;
private
SurfaceHolder
surfaceHolder
;
private
TextRenderer
.
Output
textOutput
;
private
TextRenderer
.
Output
textOutput
;
private
MetadataRenderer
.
Output
<
List
<
Id3Frame
>
>
id3Output
;
private
MetadataRenderer
.
Output
<
Metadata
>
id3Output
;
private
VideoListener
videoListener
;
private
VideoListener
videoListener
;
private
AudioRendererEventListener
audioDebugListener
;
private
AudioRendererEventListener
audioDebugListener
;
private
VideoRendererEventListener
videoDebugListener
;
private
VideoRendererEventListener
videoDebugListener
;
...
@@ -345,7 +345,7 @@ public final class SimpleExoPlayer implements ExoPlayer {
...
@@ -345,7 +345,7 @@ public final class SimpleExoPlayer implements ExoPlayer {
*
*
* @param output The output.
* @param output The output.
*/
*/
public
void
setId3Output
(
MetadataRenderer
.
Output
<
List
<
Id3Frame
>
>
output
)
{
public
void
setId3Output
(
MetadataRenderer
.
Output
<
Metadata
>
output
)
{
id3Output
=
output
;
id3Output
=
output
;
}
}
...
@@ -484,7 +484,7 @@ public final class SimpleExoPlayer implements ExoPlayer {
...
@@ -484,7 +484,7 @@ public final class SimpleExoPlayer implements ExoPlayer {
Renderer
textRenderer
=
new
TextRenderer
(
componentListener
,
mainHandler
.
getLooper
());
Renderer
textRenderer
=
new
TextRenderer
(
componentListener
,
mainHandler
.
getLooper
());
renderersList
.
add
(
textRenderer
);
renderersList
.
add
(
textRenderer
);
MetadataRenderer
<
List
<
Id3Frame
>
>
id3Renderer
=
new
MetadataRenderer
<>(
componentListener
,
MetadataRenderer
<
Metadata
>
id3Renderer
=
new
MetadataRenderer
<>(
componentListener
,
mainHandler
.
getLooper
(),
new
Id3Decoder
());
mainHandler
.
getLooper
(),
new
Id3Decoder
());
renderersList
.
add
(
id3Renderer
);
renderersList
.
add
(
id3Renderer
);
}
}
...
@@ -565,7 +565,7 @@ public final class SimpleExoPlayer implements ExoPlayer {
...
@@ -565,7 +565,7 @@ public final class SimpleExoPlayer implements ExoPlayer {
}
}
private
final
class
ComponentListener
implements
VideoRendererEventListener
,
private
final
class
ComponentListener
implements
VideoRendererEventListener
,
AudioRendererEventListener
,
TextRenderer
.
Output
,
MetadataRenderer
.
Output
<
List
<
Id3Frame
>
>,
AudioRendererEventListener
,
TextRenderer
.
Output
,
MetadataRenderer
.
Output
<
Metadata
>,
SurfaceHolder
.
Callback
{
SurfaceHolder
.
Callback
{
// VideoRendererEventListener implementation
// VideoRendererEventListener implementation
...
@@ -696,12 +696,12 @@ public final class SimpleExoPlayer implements ExoPlayer {
...
@@ -696,12 +696,12 @@ public final class SimpleExoPlayer implements ExoPlayer {
}
}
}
}
// MetadataRenderer.Output<
List<Id3Frame>
> implementation
// MetadataRenderer.Output<
Metadata
> implementation
@Override
@Override
public
void
onMetadata
(
List
<
Id3Frame
>
id3Frames
)
{
public
void
onMetadata
(
Metadata
metadata
)
{
if
(
id3Output
!=
null
)
{
if
(
id3Output
!=
null
)
{
id3Output
.
onMetadata
(
id3Frames
);
id3Output
.
onMetadata
(
metadata
);
}
}
}
}
...
...
library/src/main/java/com/google/android/exoplayer2/extractor/GaplessInfo.java
0 → 100644
View file @
18ab9634
/*
* 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
;
import
android.util.Log
;
import
java.util.regex.Matcher
;
import
java.util.regex.Pattern
;
/**
* Gapless playback information.
*/
public
final
class
GaplessInfo
{
private
static
final
String
GAPLESS_COMMENT_ID
=
"iTunSMPB"
;
private
static
final
Pattern
GAPLESS_COMMENT_PATTERN
=
Pattern
.
compile
(
"^ [0-9a-fA-F]{8} ([0-9a-fA-F]{8}) ([0-9a-fA-F]{8})"
);
/**
* The number of samples to trim from the start of the decoded audio stream.
*/
public
final
int
encoderDelay
;
/**
* The number of samples to trim from the end of the decoded audio stream.
*/
public
final
int
encoderPadding
;
/**
* Parses gapless playback information from a gapless playback comment (stored in an ID3 header
* or MPEG 4 user data), if valid and non-zero.
* @param name The comment's identifier.
* @param data The comment's payload data.
* @return the gapless playback info, or null if the provided data is not valid.
*/
public
static
GaplessInfo
createFromComment
(
String
name
,
String
data
)
{
if
(!
GAPLESS_COMMENT_ID
.
equals
(
name
))
{
return
null
;
}
else
{
Matcher
matcher
=
GAPLESS_COMMENT_PATTERN
.
matcher
(
data
);
if
(
matcher
.
find
())
{
try
{
int
encoderDelay
=
Integer
.
parseInt
(
matcher
.
group
(
1
),
16
);
int
encoderPadding
=
Integer
.
parseInt
(
matcher
.
group
(
2
),
16
);
if
(
encoderDelay
>
0
||
encoderPadding
>
0
)
{
Log
.
d
(
"ExoplayerImpl"
,
"Parsed gapless info: "
+
encoderDelay
+
" "
+
encoderPadding
);
return
new
GaplessInfo
(
encoderDelay
,
encoderPadding
);
}
}
catch
(
NumberFormatException
var5
)
{
;
}
}
// Ignore incorrectly formatted comments.
Log
.
d
(
"ExoplayerImpl"
,
"Unable to parse gapless info: "
+
data
);
return
null
;
}
}
/**
* Parses gapless playback information from an MP3 Xing header, if valid and non-zero.
*
* @param value The 24-bit value to decode.
* @return the gapless playback info, or null if the provided data is not valid.
*/
public
static
GaplessInfo
createFromXingHeaderValue
(
int
value
)
{
int
encoderDelay
=
value
>>
12
;
int
encoderPadding
=
value
&
0x0FFF
;
return
encoderDelay
>
0
||
encoderPadding
>
0
?
new
GaplessInfo
(
encoderDelay
,
encoderPadding
)
:
null
;
}
public
GaplessInfo
(
int
encoderDelay
,
int
encoderPadding
)
{
this
.
encoderDelay
=
encoderDelay
;
this
.
encoderPadding
=
encoderPadding
;
}
}
library/src/main/java/com/google/android/exoplayer2/extractor/GaplessInfoHolder.java
View file @
18ab9634
...
@@ -15,90 +15,11 @@
...
@@ -15,90 +15,11 @@
*/
*/
package
com
.
google
.
android
.
exoplayer2
.
extractor
;
package
com
.
google
.
android
.
exoplayer2
.
extractor
;
import
com.google.android.exoplayer2.Format
;
import
java.util.regex.Matcher
;
import
java.util.regex.Pattern
;
/**
/**
* Holder for gapless playback information.
* Holder for gapless playback information.
*/
*/
public
final
class
GaplessInfoHolder
{
public
final
class
GaplessInfoHolder
{
private
static
final
String
GAPLESS_COMMENT_ID
=
"iTunSMPB"
;
public
GaplessInfo
gaplessInfo
;
private
static
final
Pattern
GAPLESS_COMMENT_PATTERN
=
Pattern
.
compile
(
"^ [0-9a-fA-F]{8} ([0-9a-fA-F]{8}) ([0-9a-fA-F]{8})"
);
/**
* The number of samples to trim from the start of the decoded audio stream, or
* {@link Format#NO_VALUE} if not set.
*/
public
int
encoderDelay
;
/**
* The number of samples to trim from the end of the decoded audio stream, or
* {@link Format#NO_VALUE} if not set.
*/
public
int
encoderPadding
;
/**
* Creates a new holder for gapless playback information.
*/
public
GaplessInfoHolder
()
{
encoderDelay
=
Format
.
NO_VALUE
;
encoderPadding
=
Format
.
NO_VALUE
;
}
/**
* Populates the holder with data from an MP3 Xing header, if valid and non-zero.
*
* @param value The 24-bit value to decode.
* @return Whether the holder was populated.
*/
public
boolean
setFromXingHeaderValue
(
int
value
)
{
int
encoderDelay
=
value
>>
12
;
int
encoderPadding
=
value
&
0x0FFF
;
if
(
encoderDelay
>
0
||
encoderPadding
>
0
)
{
this
.
encoderDelay
=
encoderDelay
;
this
.
encoderPadding
=
encoderPadding
;
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.
*
* @param name The comment's identifier.
* @param data The comment's payload data.
* @return Whether the holder was populated.
*/
public
boolean
setFromComment
(
String
name
,
String
data
)
{
if
(!
GAPLESS_COMMENT_ID
.
equals
(
name
))
{
return
false
;
}
Matcher
matcher
=
GAPLESS_COMMENT_PATTERN
.
matcher
(
data
);
if
(
matcher
.
find
())
{
try
{
int
encoderDelay
=
Integer
.
parseInt
(
matcher
.
group
(
1
),
16
);
int
encoderPadding
=
Integer
.
parseInt
(
matcher
.
group
(
2
),
16
);
if
(
encoderDelay
>
0
||
encoderPadding
>
0
)
{
this
.
encoderDelay
=
encoderDelay
;
this
.
encoderPadding
=
encoderPadding
;
return
true
;
}
}
catch
(
NumberFormatException
e
)
{
// Ignore incorrectly formatted comments.
}
}
return
false
;
}
/**
* Returns whether {@link #encoderDelay} and {@link #encoderPadding} have been set.
*/
public
boolean
hasGaplessInfo
()
{
return
encoderDelay
!=
Format
.
NO_VALUE
&&
encoderPadding
!=
Format
.
NO_VALUE
;
}
}
}
library/src/main/java/com/google/android/exoplayer2/extractor/mp3/Id3Util.java
View file @
18ab9634
...
@@ -15,13 +15,13 @@
...
@@ -15,13 +15,13 @@
*/
*/
package
com
.
google
.
android
.
exoplayer2
.
extractor
.
mp3
;
package
com
.
google
.
android
.
exoplayer2
.
extractor
.
mp3
;
import
android.util.Pair
;
import
com.google.android.exoplayer2.extractor.ExtractorInput
;
import
com.google.android.exoplayer2.extractor.ExtractorInput
;
import
com.google.android.exoplayer2.extractor.GaplessInfoHolder
;
import
com.google.android.exoplayer2.metadata.Metadata
;
import
com.google.android.exoplayer2.metadata.MetadataDecoderException
;
import
com.google.android.exoplayer2.metadata.id3.Id3Decoder
;
import
com.google.android.exoplayer2.util.ParsableByteArray
;
import
com.google.android.exoplayer2.util.ParsableByteArray
;
import
com.google.android.exoplayer2.util.Util
;
import
com.google.android.exoplayer2.util.Util
;
import
java.io.IOException
;
import
java.io.IOException
;
import
java.nio.charset.Charset
;
/**
/**
* Utility for parsing ID3 version 2 metadata in MP3 files.
* Utility for parsing ID3 version 2 metadata in MP3 files.
...
@@ -34,19 +34,18 @@ import java.nio.charset.Charset;
...
@@ -34,19 +34,18 @@ import java.nio.charset.Charset;
private
static
final
int
MAXIMUM_METADATA_SIZE
=
3
*
1024
*
1024
;
private
static
final
int
MAXIMUM_METADATA_SIZE
=
3
*
1024
*
1024
;
private
static
final
int
ID3_TAG
=
Util
.
getIntegerCodeForString
(
"ID3"
);
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.
* Peeks data from the input and parses ID3 metadata
, including gapless playback information
.
*
*
* @param input The {@link ExtractorInput} from which data should be peeked.
* @param input The {@link ExtractorInput} from which data should be peeked.
* @
param out The {@link GaplessInfoHolder} to populat
e.
* @
return The metadata, if present, {@code null} otherwis
e.
* @throws IOException If an error occurred peeking from the input.
* @throws IOException If an error occurred peeking from the input.
* @throws InterruptedException If the thread was interrupted.
* @throws InterruptedException If the thread was interrupted.
*/
*/
public
static
void
parseId3
(
ExtractorInput
input
,
GaplessInfoHolder
o
ut
)
public
static
Metadata
parseId3
(
ExtractorInput
inp
ut
)
throws
IOException
,
InterruptedException
{
throws
IOException
,
InterruptedException
{
Metadata
result
=
null
;
ParsableByteArray
scratch
=
new
ParsableByteArray
(
10
);
ParsableByteArray
scratch
=
new
ParsableByteArray
(
10
);
int
peekedId3Bytes
=
0
;
int
peekedId3Bytes
=
0
;
while
(
true
)
{
while
(
true
)
{
...
@@ -60,18 +59,26 @@ import java.nio.charset.Charset;
...
@@ -60,18 +59,26 @@ import java.nio.charset.Charset;
int
minorVersion
=
scratch
.
readUnsignedByte
();
int
minorVersion
=
scratch
.
readUnsignedByte
();
int
flags
=
scratch
.
readUnsignedByte
();
int
flags
=
scratch
.
readUnsignedByte
();
int
length
=
scratch
.
readSynchSafeInt
();
int
length
=
scratch
.
readSynchSafeInt
();
if
(!
out
.
hasGaplessInfo
()
&&
canParseMetadata
(
majorVersion
,
minorVersion
,
flags
,
length
))
{
int
frameLength
=
length
+
10
;
byte
[]
frame
=
new
byte
[
length
];
input
.
peekFully
(
frame
,
0
,
length
);
try
{
parseGaplessInfo
(
new
ParsableByteArray
(
frame
),
majorVersion
,
flags
,
out
);
if
(
canParseMetadata
(
majorVersion
,
minorVersion
,
flags
,
length
))
{
}
else
{
input
.
resetPeekPosition
();
input
.
advancePeekPosition
(
length
);
byte
[]
frame
=
new
byte
[
frameLength
];
input
.
peekFully
(
frame
,
0
,
frameLength
);
return
new
Id3Decoder
().
decode
(
frame
,
frameLength
);
}
else
{
input
.
advancePeekPosition
(
length
);
}
}
catch
(
MetadataDecoderException
e
)
{
e
.
printStackTrace
();
}
}
peekedId3Bytes
+=
10
+
l
ength
;
peekedId3Bytes
+=
frameL
ength
;
}
}
input
.
resetPeekPosition
();
input
.
resetPeekPosition
();
input
.
advancePeekPosition
(
peekedId3Bytes
);
input
.
advancePeekPosition
(
peekedId3Bytes
);
return
result
;
}
}
private
static
boolean
canParseMetadata
(
int
majorVersion
,
int
minorVersion
,
int
flags
,
private
static
boolean
canParseMetadata
(
int
majorVersion
,
int
minorVersion
,
int
flags
,
...
@@ -83,211 +90,6 @@ import java.nio.charset.Charset;
...
@@ -83,211 +90,6 @@ import java.nio.charset.Charset;
&&
!(
majorVersion
==
4
&&
(
flags
&
0x0F
)
!=
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
()
{}
private
Id3Util
()
{}
}
}
library/src/main/java/com/google/android/exoplayer2/extractor/mp3/Mp3Extractor.java
View file @
18ab9634
...
@@ -22,11 +22,12 @@ import com.google.android.exoplayer2.extractor.Extractor;
...
@@ -22,11 +22,12 @@ import com.google.android.exoplayer2.extractor.Extractor;
import
com.google.android.exoplayer2.extractor.ExtractorInput
;
import
com.google.android.exoplayer2.extractor.ExtractorInput
;
import
com.google.android.exoplayer2.extractor.ExtractorOutput
;
import
com.google.android.exoplayer2.extractor.ExtractorOutput
;
import
com.google.android.exoplayer2.extractor.ExtractorsFactory
;
import
com.google.android.exoplayer2.extractor.ExtractorsFactory
;
import
com.google.android.exoplayer2.extractor.GaplessInfo
Holder
;
import
com.google.android.exoplayer2.extractor.GaplessInfo
;
import
com.google.android.exoplayer2.extractor.MpegAudioHeader
;
import
com.google.android.exoplayer2.extractor.MpegAudioHeader
;
import
com.google.android.exoplayer2.extractor.PositionHolder
;
import
com.google.android.exoplayer2.extractor.PositionHolder
;
import
com.google.android.exoplayer2.extractor.SeekMap
;
import
com.google.android.exoplayer2.extractor.SeekMap
;
import
com.google.android.exoplayer2.extractor.TrackOutput
;
import
com.google.android.exoplayer2.extractor.TrackOutput
;
import
com.google.android.exoplayer2.metadata.Metadata
;
import
com.google.android.exoplayer2.util.ParsableByteArray
;
import
com.google.android.exoplayer2.util.ParsableByteArray
;
import
com.google.android.exoplayer2.util.Util
;
import
com.google.android.exoplayer2.util.Util
;
import
java.io.EOFException
;
import
java.io.EOFException
;
...
@@ -69,7 +70,7 @@ public final class Mp3Extractor implements Extractor {
...
@@ -69,7 +70,7 @@ public final class Mp3Extractor implements Extractor {
private
final
long
forcedFirstSampleTimestampUs
;
private
final
long
forcedFirstSampleTimestampUs
;
private
final
ParsableByteArray
scratch
;
private
final
ParsableByteArray
scratch
;
private
final
MpegAudioHeader
synchronizedHeader
;
private
final
MpegAudioHeader
synchronizedHeader
;
private
final
GaplessInfoHolder
gaplessInfoHolder
;
private
Metadata
metadata
;
// Extractor outputs.
// Extractor outputs.
private
ExtractorOutput
extractorOutput
;
private
ExtractorOutput
extractorOutput
;
...
@@ -99,7 +100,6 @@ public final class Mp3Extractor implements Extractor {
...
@@ -99,7 +100,6 @@ public final class Mp3Extractor implements Extractor {
this
.
forcedFirstSampleTimestampUs
=
forcedFirstSampleTimestampUs
;
this
.
forcedFirstSampleTimestampUs
=
forcedFirstSampleTimestampUs
;
scratch
=
new
ParsableByteArray
(
4
);
scratch
=
new
ParsableByteArray
(
4
);
synchronizedHeader
=
new
MpegAudioHeader
();
synchronizedHeader
=
new
MpegAudioHeader
();
gaplessInfoHolder
=
new
GaplessInfoHolder
();
basisTimeUs
=
C
.
TIME_UNSET
;
basisTimeUs
=
C
.
TIME_UNSET
;
}
}
...
@@ -137,10 +137,21 @@ public final class Mp3Extractor implements Extractor {
...
@@ -137,10 +137,21 @@ public final class Mp3Extractor implements Extractor {
if
(
seeker
==
null
)
{
if
(
seeker
==
null
)
{
seeker
=
setupSeeker
(
input
);
seeker
=
setupSeeker
(
input
);
extractorOutput
.
seekMap
(
seeker
);
extractorOutput
.
seekMap
(
seeker
);
trackOutput
.
format
(
Format
.
createAudioSampleFormat
(
null
,
synchronizedHeader
.
mimeType
,
null
,
Format
.
NO_VALUE
,
MpegAudioHeader
.
MAX_FRAME_SIZE_BYTES
,
synchronizedHeader
.
channels
,
GaplessInfo
gaplessInfo
=
metadata
!=
null
?
metadata
.
getGaplessInfo
()
:
null
;
synchronizedHeader
.
sampleRate
,
Format
.
NO_VALUE
,
gaplessInfoHolder
.
encoderDelay
,
gaplessInfoHolder
.
encoderPadding
,
null
,
null
,
0
,
null
));
Format
format
=
Format
.
createAudioSampleFormat
(
null
,
synchronizedHeader
.
mimeType
,
null
,
Format
.
NO_VALUE
,
MpegAudioHeader
.
MAX_FRAME_SIZE_BYTES
,
synchronizedHeader
.
channels
,
synchronizedHeader
.
sampleRate
,
Format
.
NO_VALUE
,
gaplessInfo
!=
null
?
gaplessInfo
.
encoderDelay
:
Format
.
NO_VALUE
,
gaplessInfo
!=
null
?
gaplessInfo
.
encoderPadding
:
Format
.
NO_VALUE
,
null
,
null
,
0
,
null
);
if
(
metadata
!=
null
)
{
format
=
format
.
copyWithMetadata
(
metadata
);
}
trackOutput
.
format
(
format
);
}
}
return
readSample
(
input
);
return
readSample
(
input
);
}
}
...
@@ -220,7 +231,7 @@ public final class Mp3Extractor implements Extractor {
...
@@ -220,7 +231,7 @@ public final class Mp3Extractor implements Extractor {
int
peekedId3Bytes
=
0
;
int
peekedId3Bytes
=
0
;
input
.
resetPeekPosition
();
input
.
resetPeekPosition
();
if
(
input
.
getPosition
()
==
0
)
{
if
(
input
.
getPosition
()
==
0
)
{
Id3Util
.
parseId3
(
input
,
gaplessInfoHolder
);
metadata
=
Id3Util
.
parseId3
(
input
);
peekedId3Bytes
=
(
int
)
input
.
getPeekPosition
();
peekedId3Bytes
=
(
int
)
input
.
getPeekPosition
();
if
(!
sniffing
)
{
if
(!
sniffing
)
{
input
.
skipFully
(
peekedId3Bytes
);
input
.
skipFully
(
peekedId3Bytes
);
...
@@ -303,13 +314,16 @@ public final class Mp3Extractor implements Extractor {
...
@@ -303,13 +314,16 @@ public final class Mp3Extractor implements Extractor {
Seeker
seeker
=
null
;
Seeker
seeker
=
null
;
if
(
headerData
==
XING_HEADER
||
headerData
==
INFO_HEADER
)
{
if
(
headerData
==
XING_HEADER
||
headerData
==
INFO_HEADER
)
{
seeker
=
XingSeeker
.
create
(
synchronizedHeader
,
frame
,
position
,
length
);
seeker
=
XingSeeker
.
create
(
synchronizedHeader
,
frame
,
position
,
length
);
if
(
seeker
!=
null
&&
!
gaplessInfoHolder
.
hasGaplessInfo
()
)
{
if
(
seeker
!=
null
&&
metadata
==
null
||
metadata
.
getGaplessInfo
()
==
null
)
{
// If there is a Xing header, read gapless playback metadata at a fixed offset.
// If there is a Xing header, read gapless playback metadata at a fixed offset.
input
.
resetPeekPosition
();
input
.
resetPeekPosition
();
input
.
advancePeekPosition
(
xingBase
+
141
);
input
.
advancePeekPosition
(
xingBase
+
141
);
input
.
peekFully
(
scratch
.
data
,
0
,
3
);
input
.
peekFully
(
scratch
.
data
,
0
,
3
);
scratch
.
setPosition
(
0
);
scratch
.
setPosition
(
0
);
gaplessInfoHolder
.
setFromXingHeaderValue
(
scratch
.
readUnsignedInt24
());
GaplessInfo
gaplessInfo
=
GaplessInfo
.
createFromXingHeaderValue
(
scratch
.
readUnsignedInt24
());
metadata
=
metadata
!=
null
?
metadata
.
withGaplessInfo
(
gaplessInfo
)
:
new
Metadata
(
null
,
gaplessInfo
);
}
}
input
.
skipFully
(
synchronizedHeader
.
frameSize
);
input
.
skipFully
(
synchronizedHeader
.
frameSize
);
}
else
{
}
else
{
...
...
library/src/main/java/com/google/android/exoplayer2/extractor/mp4/AtomParsers.java
View file @
18ab9634
...
@@ -21,7 +21,15 @@ import com.google.android.exoplayer2.Format;
...
@@ -21,7 +21,15 @@ import com.google.android.exoplayer2.Format;
import
com.google.android.exoplayer2.ParserException
;
import
com.google.android.exoplayer2.ParserException
;
import
com.google.android.exoplayer2.audio.Ac3Util
;
import
com.google.android.exoplayer2.audio.Ac3Util
;
import
com.google.android.exoplayer2.drm.DrmInitData
;
import
com.google.android.exoplayer2.drm.DrmInitData
;
import
com.google.android.exoplayer2.extractor.GaplessInfo
;
import
com.google.android.exoplayer2.extractor.GaplessInfoHolder
;
import
com.google.android.exoplayer2.extractor.GaplessInfoHolder
;
import
com.google.android.exoplayer2.metadata.Metadata
;
import
com.google.android.exoplayer2.metadata.MetadataBuilder
;
import
com.google.android.exoplayer2.metadata.id3.BinaryFrame
;
import
com.google.android.exoplayer2.metadata.id3.CommentFrame
;
import
com.google.android.exoplayer2.metadata.id3.Id3Frame
;
import
com.google.android.exoplayer2.metadata.id3.Id3Decoder
;
import
com.google.android.exoplayer2.metadata.id3.TextInformationFrame
;
import
com.google.android.exoplayer2.util.Assertions
;
import
com.google.android.exoplayer2.util.Assertions
;
import
com.google.android.exoplayer2.util.CodecSpecificDataUtil
;
import
com.google.android.exoplayer2.util.CodecSpecificDataUtil
;
import
com.google.android.exoplayer2.util.MimeTypes
;
import
com.google.android.exoplayer2.util.MimeTypes
;
...
@@ -270,7 +278,7 @@ import java.util.List;
...
@@ -270,7 +278,7 @@ import java.util.List;
flags
=
rechunkedResults
.
flags
;
flags
=
rechunkedResults
.
flags
;
}
}
if
(
track
.
editListDurations
==
null
||
gaplessInfoHolder
.
hasGaplessInfo
()
)
{
if
(
track
.
editListDurations
==
null
||
gaplessInfoHolder
.
gaplessInfo
!=
null
)
{
// There is no edit list, or we are ignoring it as we already have gapless metadata to apply.
// There is no edit list, or we are ignoring it as we already have gapless metadata to apply.
// This implementation does not support applying both gapless metadata and an edit list.
// This implementation does not support applying both gapless metadata and an edit list.
Util
.
scaleLargeTimestampsInPlace
(
timestamps
,
C
.
MICROS_PER_SECOND
,
track
.
timescale
);
Util
.
scaleLargeTimestampsInPlace
(
timestamps
,
C
.
MICROS_PER_SECOND
,
track
.
timescale
);
...
@@ -299,10 +307,9 @@ import java.util.List;
...
@@ -299,10 +307,9 @@ import java.util.List;
track
.
format
.
sampleRate
,
track
.
timescale
);
track
.
format
.
sampleRate
,
track
.
timescale
);
long
encoderPadding
=
Util
.
scaleLargeTimestamp
(
paddingTimeUnits
,
long
encoderPadding
=
Util
.
scaleLargeTimestamp
(
paddingTimeUnits
,
track
.
format
.
sampleRate
,
track
.
timescale
);
track
.
format
.
sampleRate
,
track
.
timescale
);
if
((
encoderDelay
!=
0
||
encoderPadding
!=
0
)
&&
encoderDelay
<=
Integer
.
MAX_VALUE
if
((
encoderDelay
>
0
||
encoderPadding
>
0
)
&&
encoderDelay
<=
Integer
.
MAX_VALUE
&&
encoderPadding
<=
Integer
.
MAX_VALUE
)
{
&&
encoderPadding
<=
Integer
.
MAX_VALUE
)
{
gaplessInfoHolder
.
encoderDelay
=
(
int
)
encoderDelay
;
gaplessInfoHolder
.
gaplessInfo
=
new
GaplessInfo
((
int
)
encoderDelay
,
(
int
)
encoderPadding
);
gaplessInfoHolder
.
encoderPadding
=
(
int
)
encoderPadding
;
Util
.
scaleLargeTimestampsInPlace
(
timestamps
,
C
.
MICROS_PER_SECOND
,
track
.
timescale
);
Util
.
scaleLargeTimestampsInPlace
(
timestamps
,
C
.
MICROS_PER_SECOND
,
track
.
timescale
);
return
new
TrackSampleTable
(
offsets
,
sizes
,
maximumSize
,
timestamps
,
flags
);
return
new
TrackSampleTable
(
offsets
,
sizes
,
maximumSize
,
timestamps
,
flags
);
}
}
...
@@ -387,17 +394,17 @@ import java.util.List;
...
@@ -387,17 +394,17 @@ import java.util.List;
}
}
/**
/**
* Parses a udta atom.
* Parses a udta atom
for metadata, including gapless playback information
.
*
*
* @param udtaAtom The udta (user data) atom to decode.
* @param udtaAtom The udta (user data) atom to decode.
* @param isQuickTime True for QuickTime media. False otherwise.
* @param isQuickTime True for QuickTime media. False otherwise.
* @
param out {@link GaplessInfoHolder} to populate with gapless playback information
.
* @
return metadata stored in the user data, or {@code null} if not present
.
*/
*/
public
static
void
parseUdta
(
Atom
.
LeafAtom
udtaAtom
,
boolean
isQuickTime
,
GaplessInfoHolder
out
)
{
public
static
Metadata
parseUdta
(
Atom
.
LeafAtom
udtaAtom
,
boolean
isQuickTime
)
{
if
(
isQuickTime
)
{
if
(
isQuickTime
)
{
// Meta boxes are regular boxes rather than full boxes in QuickTime. For now, don't try and
// Meta boxes are regular boxes rather than full boxes in QuickTime. For now, don't try and
// decode one.
// decode one.
return
;
return
null
;
}
}
ParsableByteArray
udtaData
=
udtaAtom
.
data
;
ParsableByteArray
udtaData
=
udtaAtom
.
data
;
udtaData
.
setPosition
(
Atom
.
HEADER_SIZE
);
udtaData
.
setPosition
(
Atom
.
HEADER_SIZE
);
...
@@ -407,14 +414,15 @@ import java.util.List;
...
@@ -407,14 +414,15 @@ import java.util.List;
if
(
atomType
==
Atom
.
TYPE_meta
)
{
if
(
atomType
==
Atom
.
TYPE_meta
)
{
udtaData
.
setPosition
(
udtaData
.
getPosition
()
-
Atom
.
HEADER_SIZE
);
udtaData
.
setPosition
(
udtaData
.
getPosition
()
-
Atom
.
HEADER_SIZE
);
udtaData
.
setLimit
(
udtaData
.
getPosition
()
+
atomSize
);
udtaData
.
setLimit
(
udtaData
.
getPosition
()
+
atomSize
);
parseMetaAtom
(
udtaData
,
out
);
parseMetaAtom
(
udtaData
);
break
;
break
;
}
}
udtaData
.
skipBytes
(
atomSize
-
Atom
.
HEADER_SIZE
);
udtaData
.
skipBytes
(
atomSize
-
Atom
.
HEADER_SIZE
);
}
}
return
null
;
}
}
private
static
void
parseMetaAtom
(
ParsableByteArray
data
,
GaplessInfoHolder
out
)
{
private
static
Metadata
parseMetaAtom
(
ParsableByteArray
data
)
{
data
.
skipBytes
(
Atom
.
FULL_HEADER_SIZE
);
data
.
skipBytes
(
Atom
.
FULL_HEADER_SIZE
);
ParsableByteArray
ilst
=
new
ParsableByteArray
();
ParsableByteArray
ilst
=
new
ParsableByteArray
();
while
(
data
.
bytesLeft
()
>=
Atom
.
HEADER_SIZE
)
{
while
(
data
.
bytesLeft
()
>=
Atom
.
HEADER_SIZE
)
{
...
@@ -423,48 +431,334 @@ import java.util.List;
...
@@ -423,48 +431,334 @@ import java.util.List;
if
(
atomType
==
Atom
.
TYPE_ilst
)
{
if
(
atomType
==
Atom
.
TYPE_ilst
)
{
ilst
.
reset
(
data
.
data
,
data
.
getPosition
()
+
payloadSize
);
ilst
.
reset
(
data
.
data
,
data
.
getPosition
()
+
payloadSize
);
ilst
.
setPosition
(
data
.
getPosition
());
ilst
.
setPosition
(
data
.
getPosition
());
parseIlst
(
ilst
,
ou
t
);
Metadata
result
=
parseIlst
(
ils
t
);
if
(
out
.
hasGaplessInfo
()
)
{
if
(
result
!=
null
)
{
return
;
return
result
;
}
}
}
}
data
.
skipBytes
(
payloadSize
);
data
.
skipBytes
(
payloadSize
);
}
}
return
null
;
}
}
private
static
void
parseIlst
(
ParsableByteArray
ilst
,
GaplessInfoHolder
out
)
{
private
static
Metadata
parseIlst
(
ParsableByteArray
ilst
)
{
MetadataBuilder
builder
=
new
MetadataBuilder
();
while
(
ilst
.
bytesLeft
()
>
0
)
{
while
(
ilst
.
bytesLeft
()
>
0
)
{
int
position
=
ilst
.
getPosition
();
int
position
=
ilst
.
getPosition
();
int
endPosition
=
position
+
ilst
.
readInt
();
int
endPosition
=
position
+
ilst
.
readInt
();
int
type
=
ilst
.
readInt
();
int
type
=
ilst
.
readInt
();
if
(
type
==
Atom
.
TYPE_DASHES
)
{
parseIlstElement
(
ilst
,
type
,
endPosition
,
builder
);
String
lastCommentMean
=
null
;
ilst
.
setPosition
(
endPosition
);
String
lastCommentName
=
null
;
}
String
lastCommentData
=
null
;
while
(
ilst
.
getPosition
()
<
endPosition
)
{
return
builder
.
build
();
int
length
=
ilst
.
readInt
()
-
Atom
.
FULL_HEADER_SIZE
;
}
int
key
=
ilst
.
readInt
();
ilst
.
skipBytes
(
4
);
private
static
final
String
P1
=
"\u00a9"
;
if
(
key
==
Atom
.
TYPE_mean
)
{
private
static
final
String
P2
=
"\ufffd"
;
lastCommentMean
=
ilst
.
readString
(
length
);
private
static
final
int
TYPE_NAME_1
=
Util
.
getIntegerCodeForString
(
P1
+
"nam"
);
}
else
if
(
key
==
Atom
.
TYPE_name
)
{
private
static
final
int
TYPE_NAME_2
=
Util
.
getIntegerCodeForString
(
P2
+
"nam"
);
lastCommentName
=
ilst
.
readString
(
length
);
private
static
final
int
TYPE_NAME_3
=
Util
.
getIntegerCodeForString
(
P1
+
"trk"
);
}
else
if
(
key
==
Atom
.
TYPE_data
)
{
private
static
final
int
TYPE_NAME_4
=
Util
.
getIntegerCodeForString
(
P2
+
"trk"
);
ilst
.
skipBytes
(
4
);
private
static
final
int
TYPE_COMMENT_1
=
Util
.
getIntegerCodeForString
(
P1
+
"cmt"
);
lastCommentData
=
ilst
.
readString
(
length
-
4
);
private
static
final
int
TYPE_COMMENT_2
=
Util
.
getIntegerCodeForString
(
P2
+
"cmt"
);
}
else
{
private
static
final
int
TYPE_YEAR_1
=
Util
.
getIntegerCodeForString
(
P1
+
"day"
);
ilst
.
skipBytes
(
length
);
private
static
final
int
TYPE_YEAR_2
=
Util
.
getIntegerCodeForString
(
P2
+
"day"
);
private
static
final
int
TYPE_ARTIST_1
=
Util
.
getIntegerCodeForString
(
P1
+
"ART"
);
private
static
final
int
TYPE_ARTIST_2
=
Util
.
getIntegerCodeForString
(
P2
+
"ART"
);
private
static
final
int
TYPE_ENCODER_1
=
Util
.
getIntegerCodeForString
(
P1
+
"too"
);
private
static
final
int
TYPE_ENCODER_2
=
Util
.
getIntegerCodeForString
(
P2
+
"too"
);
private
static
final
int
TYPE_ALBUM_1
=
Util
.
getIntegerCodeForString
(
P1
+
"alb"
);
private
static
final
int
TYPE_ALBUM_2
=
Util
.
getIntegerCodeForString
(
P2
+
"alb"
);
private
static
final
int
TYPE_COMPOSER_1
=
Util
.
getIntegerCodeForString
(
P1
+
"com"
);
private
static
final
int
TYPE_COMPOSER_2
=
Util
.
getIntegerCodeForString
(
P2
+
"com"
);
private
static
final
int
TYPE_COMPOSER_3
=
Util
.
getIntegerCodeForString
(
P1
+
"wrt"
);
private
static
final
int
TYPE_COMPOSER_4
=
Util
.
getIntegerCodeForString
(
P2
+
"wrt"
);
private
static
final
int
TYPE_LYRICS_1
=
Util
.
getIntegerCodeForString
(
P1
+
"lyr"
);
private
static
final
int
TYPE_LYRICS_2
=
Util
.
getIntegerCodeForString
(
P2
+
"lyr"
);
private
static
final
int
TYPE_GENRE_1
=
Util
.
getIntegerCodeForString
(
P1
+
"gen"
);
private
static
final
int
TYPE_GENRE_2
=
Util
.
getIntegerCodeForString
(
P2
+
"gen"
);
private
static
final
int
TYPE_STANDARD_GENRE
=
Util
.
getIntegerCodeForString
(
"gnre"
);
private
static
final
int
TYPE_GROUPING_1
=
Util
.
getIntegerCodeForString
(
P1
+
"grp"
);
private
static
final
int
TYPE_GROUPING_2
=
Util
.
getIntegerCodeForString
(
P2
+
"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"
);
private
static
final
int
TYPE_SORT_SHOW
=
Util
.
getIntegerCodeForString
(
"sosn"
);
private
static
final
int
TYPE_GAPLESS_ALBUM
=
Util
.
getIntegerCodeForString
(
"pgap"
);
private
static
final
int
TYPE_SHOW
=
Util
.
getIntegerCodeForString
(
"tvsh"
);
// TBD: covr = cover art, various account and iTunes specific attributes, more TV attributes
private
static
void
parseIlstElement
(
ParsableByteArray
ilst
,
int
type
,
int
endPosition
,
MetadataBuilder
builder
)
{
if
(
type
==
TYPE_NAME_1
||
type
==
TYPE_NAME_2
||
type
==
TYPE_NAME_3
||
type
==
TYPE_NAME_4
)
{
parseTextAttribute
(
builder
,
"TIT2"
,
ilst
,
endPosition
);
}
else
if
(
type
==
TYPE_COMMENT_1
||
type
==
TYPE_COMMENT_2
)
{
parseCommentAttribute
(
builder
,
"COMM"
,
ilst
,
endPosition
);
}
else
if
(
type
==
TYPE_YEAR_1
||
type
==
TYPE_YEAR_2
)
{
parseTextAttribute
(
builder
,
"TDRC"
,
ilst
,
endPosition
);
}
else
if
(
type
==
TYPE_ARTIST_1
||
type
==
TYPE_ARTIST_2
)
{
parseTextAttribute
(
builder
,
"TPE1"
,
ilst
,
endPosition
);
}
else
if
(
type
==
TYPE_ENCODER_1
||
type
==
TYPE_ENCODER_2
)
{
parseTextAttribute
(
builder
,
"TSSE"
,
ilst
,
endPosition
);
}
else
if
(
type
==
TYPE_ALBUM_1
||
type
==
TYPE_ALBUM_2
)
{
parseTextAttribute
(
builder
,
"TALB"
,
ilst
,
endPosition
);
}
else
if
(
type
==
TYPE_COMPOSER_1
||
type
==
TYPE_COMPOSER_2
||
type
==
TYPE_COMPOSER_3
||
type
==
TYPE_COMPOSER_4
)
{
parseTextAttribute
(
builder
,
"TCOM"
,
ilst
,
endPosition
);
}
else
if
(
type
==
TYPE_LYRICS_1
||
type
==
TYPE_LYRICS_2
)
{
parseTextAttribute
(
builder
,
"lyrics"
,
ilst
,
endPosition
);
}
else
if
(
type
==
TYPE_STANDARD_GENRE
)
{
parseStandardGenreAttribute
(
builder
,
"TCON"
,
ilst
,
endPosition
);
}
else
if
(
type
==
TYPE_GENRE_1
||
type
==
TYPE_GENRE_2
)
{
parseTextAttribute
(
builder
,
"TCON"
,
ilst
,
endPosition
);
}
else
if
(
type
==
TYPE_GROUPING_1
||
type
==
TYPE_GROUPING_2
)
{
parseTextAttribute
(
builder
,
"TIT1"
,
ilst
,
endPosition
);
}
else
if
(
type
==
TYPE_DISK_NUMBER
)
{
parseIndexAndCountAttribute
(
builder
,
"TPOS"
,
ilst
,
endPosition
);
}
else
if
(
type
==
TYPE_TRACK_NUMBER
)
{
parseIndexAndCountAttribute
(
builder
,
"TRCK"
,
ilst
,
endPosition
);
}
else
if
(
type
==
TYPE_TEMPO
)
{
parseIntegerAttribute
(
builder
,
"TBPM"
,
ilst
,
endPosition
);
}
else
if
(
type
==
TYPE_COMPILATION
)
{
parseBooleanAttribute
(
builder
,
"TCMP"
,
ilst
,
endPosition
);
}
else
if
(
type
==
TYPE_ALBUM_ARTIST
)
{
parseTextAttribute
(
builder
,
"TPE2"
,
ilst
,
endPosition
);
}
else
if
(
type
==
TYPE_SORT_TRACK_NAME
)
{
parseTextAttribute
(
builder
,
"TSOT"
,
ilst
,
endPosition
);
}
else
if
(
type
==
TYPE_SORT_ALBUM
)
{
parseTextAttribute
(
builder
,
"TSO2"
,
ilst
,
endPosition
);
}
else
if
(
type
==
TYPE_SORT_ARTIST
)
{
parseTextAttribute
(
builder
,
"TSOA"
,
ilst
,
endPosition
);
}
else
if
(
type
==
TYPE_SORT_ALBUM_ARTIST
)
{
parseTextAttribute
(
builder
,
"TSOP"
,
ilst
,
endPosition
);
}
else
if
(
type
==
TYPE_SORT_COMPOSER
)
{
parseTextAttribute
(
builder
,
"TSOC"
,
ilst
,
endPosition
);
}
else
if
(
type
==
TYPE_SORT_SHOW
)
{
parseTextAttribute
(
builder
,
"sortShow"
,
ilst
,
endPosition
);
}
else
if
(
type
==
TYPE_GAPLESS_ALBUM
)
{
parseBooleanAttribute
(
builder
,
"gaplessAlbum"
,
ilst
,
endPosition
);
}
else
if
(
type
==
TYPE_SHOW
)
{
parseTextAttribute
(
builder
,
"show"
,
ilst
,
endPosition
);
}
else
if
(
type
==
Atom
.
TYPE_DASHES
)
{
parseExtendedAttribute
(
builder
,
ilst
,
endPosition
);
}
}
private
static
void
parseTextAttribute
(
MetadataBuilder
builder
,
String
attributeName
,
ParsableByteArray
ilst
,
int
endPosition
)
{
int
length
=
ilst
.
readInt
()
-
Atom
.
FULL_HEADER_SIZE
;
int
key
=
ilst
.
readInt
();
ilst
.
skipBytes
(
4
);
if
(
key
==
Atom
.
TYPE_data
)
{
ilst
.
skipBytes
(
4
);
String
value
=
ilst
.
readNullTerminatedString
(
length
-
4
);
Id3Frame
frame
=
new
TextInformationFrame
(
attributeName
,
value
);
builder
.
add
(
frame
);
}
else
{
ilst
.
skipBytes
(
length
);
}
}
private
static
void
parseCommentAttribute
(
MetadataBuilder
builder
,
String
attributeName
,
ParsableByteArray
ilst
,
int
endPosition
)
{
int
length
=
ilst
.
readInt
()
-
Atom
.
FULL_HEADER_SIZE
;
int
key
=
ilst
.
readInt
();
ilst
.
skipBytes
(
4
);
if
(
key
==
Atom
.
TYPE_data
)
{
ilst
.
skipBytes
(
4
);
String
value
=
ilst
.
readNullTerminatedString
(
length
-
4
);
Id3Frame
frame
=
new
CommentFrame
(
"eng"
,
attributeName
,
value
);
builder
.
add
(
frame
);
}
else
{
ilst
.
skipBytes
(
length
);
}
}
private
static
void
parseBooleanAttribute
(
MetadataBuilder
builder
,
String
attributeName
,
ParsableByteArray
ilst
,
int
endPosition
)
{
int
length
=
ilst
.
readInt
()
-
Atom
.
FULL_HEADER_SIZE
;
int
key
=
ilst
.
readInt
();
ilst
.
skipBytes
(
4
);
if
(
key
==
Atom
.
TYPE_data
)
{
Object
value
=
parseDataBox
(
ilst
,
length
);
if
(
value
instanceof
Integer
)
{
int
n
=
(
Integer
)
value
;
String
s
=
n
==
0
?
"0"
:
"1"
;
Id3Frame
frame
=
new
TextInformationFrame
(
attributeName
,
s
);
builder
.
add
(
frame
);
}
}
else
{
ilst
.
skipBytes
(
length
);
}
}
private
static
void
parseIntegerAttribute
(
MetadataBuilder
builder
,
String
attributeName
,
ParsableByteArray
ilst
,
int
endPosition
)
{
int
length
=
ilst
.
readInt
()
-
Atom
.
FULL_HEADER_SIZE
;
int
key
=
ilst
.
readInt
();
ilst
.
skipBytes
(
4
);
if
(
key
==
Atom
.
TYPE_data
)
{
Object
value
=
parseDataBox
(
ilst
,
length
);
if
(
value
instanceof
Integer
)
{
int
n
=
(
Integer
)
value
;
String
s
=
""
+
n
;
Id3Frame
frame
=
new
TextInformationFrame
(
attributeName
,
s
);
builder
.
add
(
frame
);
}
}
else
{
ilst
.
skipBytes
(
length
);
}
}
private
static
void
parseIndexAndCountAttribute
(
MetadataBuilder
builder
,
String
attributeName
,
ParsableByteArray
ilst
,
int
endPosition
)
{
int
length
=
ilst
.
readInt
()
-
Atom
.
FULL_HEADER_SIZE
;
int
key
=
ilst
.
readInt
();
ilst
.
skipBytes
(
4
);
if
(
key
==
Atom
.
TYPE_data
)
{
Object
value
=
parseDataBox
(
ilst
,
length
);
if
(
value
instanceof
byte
[])
{
byte
[]
bytes
=
(
byte
[])
value
;
if
(
bytes
.
length
==
8
)
{
int
index
=
(
bytes
[
2
]
<<
8
)
+
(
bytes
[
3
]
&
0xFF
);
int
count
=
(
bytes
[
4
]
<<
8
)
+
(
bytes
[
5
]
&
0xFF
);
if
(
index
>
0
)
{
String
s
=
""
+
index
;
if
(
count
>
0
)
{
s
=
s
+
"/"
+
count
;
}
Id3Frame
frame
=
new
TextInformationFrame
(
attributeName
,
s
);
builder
.
add
(
frame
);
}
}
}
}
if
(
lastCommentName
!=
null
&&
lastCommentData
!=
null
}
&&
"com.apple.iTunes"
.
equals
(
lastCommentMean
))
{
}
else
{
out
.
setFromComment
(
lastCommentName
,
lastCommentData
);
ilst
.
skipBytes
(
length
);
break
;
}
}
private
static
void
parseStandardGenreAttribute
(
MetadataBuilder
builder
,
String
attributeName
,
ParsableByteArray
ilst
,
int
endPosition
)
{
int
length
=
ilst
.
readInt
()
-
Atom
.
FULL_HEADER_SIZE
;
int
key
=
ilst
.
readInt
();
ilst
.
skipBytes
(
4
);
if
(
key
==
Atom
.
TYPE_data
)
{
Object
value
=
parseDataBox
(
ilst
,
length
);
if
(
value
instanceof
byte
[])
{
byte
[]
bytes
=
(
byte
[])
value
;
if
(
bytes
.
length
==
2
)
{
int
code
=
(
bytes
[
0
]
<<
8
)
+
(
bytes
[
1
]
&
0xFF
);
String
s
=
Id3Decoder
.
decodeGenre
(
code
);
if
(
s
!=
null
)
{
Id3Frame
frame
=
new
TextInformationFrame
(
attributeName
,
s
);
builder
.
add
(
frame
);
}
}
}
}
}
else
{
ilst
.
skipBytes
(
length
);
}
}
private
static
void
parseExtendedAttribute
(
MetadataBuilder
builder
,
ParsableByteArray
ilst
,
int
endPosition
)
{
String
domain
=
null
;
String
name
=
null
;
Object
value
=
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
)
{
domain
=
ilst
.
readNullTerminatedString
(
length
);
}
else
if
(
key
==
Atom
.
TYPE_name
)
{
name
=
ilst
.
readNullTerminatedString
(
length
);
}
else
if
(
key
==
Atom
.
TYPE_data
)
{
value
=
parseDataBox
(
ilst
,
length
);
}
else
{
}
else
{
ilst
.
s
etPosition
(
endPosition
);
ilst
.
s
kipBytes
(
length
);
}
}
}
}
if
(
value
!=
null
)
{
if
(
Util
.
areEqual
(
domain
,
"com.apple.iTunes"
)
&&
Util
.
areEqual
(
name
,
"iTunSMPB"
))
{
String
s
=
value
instanceof
byte
[]
?
new
String
((
byte
[])
value
)
:
value
.
toString
();
builder
.
setGaplessInfo
(
GaplessInfo
.
createFromComment
(
"iTunSMPB"
,
s
));
}
if
(
Util
.
areEqual
(
domain
,
"com.apple.iTunes"
)
&&
Util
.
areEqual
(
name
,
"iTunNORM"
)
&&
(
value
instanceof
byte
[]))
{
String
s
=
new
String
((
byte
[])
value
);
Id3Frame
frame
=
new
CommentFrame
(
"eng"
,
"iTunNORM"
,
s
);
builder
.
add
(
frame
);
}
else
if
(
domain
!=
null
&&
name
!=
null
)
{
String
extendedName
=
domain
+
"."
+
name
;
if
(
value
instanceof
String
)
{
Id3Frame
frame
=
new
TextInformationFrame
(
extendedName
,
(
String
)
value
);
builder
.
add
(
frame
);
}
else
if
(
value
instanceof
Integer
)
{
Id3Frame
frame
=
new
TextInformationFrame
(
extendedName
,
value
.
toString
());
builder
.
add
(
frame
);
}
else
if
(
value
instanceof
byte
[])
{
byte
[]
bb
=
(
byte
[])
value
;
Id3Frame
frame
=
new
BinaryFrame
(
extendedName
,
bb
);
builder
.
add
(
frame
);
}
}
}
}
private
static
Object
parseDataBox
(
ParsableByteArray
ilst
,
int
length
)
{
int
versionAndFlags
=
ilst
.
readInt
();
int
flags
=
versionAndFlags
&
0xFFFFFF
;
boolean
isText
=
(
flags
==
1
);
boolean
isData
=
(
flags
==
0
);
boolean
isImageData
=
(
flags
==
0xD
);
boolean
isInteger
=
(
flags
==
21
);
int
dataLength
=
length
-
4
;
if
(
isText
)
{
return
ilst
.
readNullTerminatedString
(
dataLength
);
}
else
if
(
isInteger
)
{
if
(
dataLength
==
1
)
{
return
ilst
.
readUnsignedByte
();
}
else
if
(
dataLength
==
2
)
{
return
ilst
.
readUnsignedShort
();
}
else
{
ilst
.
skipBytes
(
dataLength
);
return
null
;
}
}
else
if
(
isData
)
{
byte
[]
bytes
=
new
byte
[
dataLength
];
ilst
.
readBytes
(
bytes
,
0
,
dataLength
);
return
bytes
;
}
else
{
ilst
.
skipBytes
(
dataLength
);
return
null
;
}
}
}
/**
/**
...
...
library/src/main/java/com/google/android/exoplayer2/extractor/mp4/Mp4Extractor.java
View file @
18ab9634
...
@@ -22,11 +22,13 @@ import com.google.android.exoplayer2.extractor.Extractor;
...
@@ -22,11 +22,13 @@ import com.google.android.exoplayer2.extractor.Extractor;
import
com.google.android.exoplayer2.extractor.ExtractorInput
;
import
com.google.android.exoplayer2.extractor.ExtractorInput
;
import
com.google.android.exoplayer2.extractor.ExtractorOutput
;
import
com.google.android.exoplayer2.extractor.ExtractorOutput
;
import
com.google.android.exoplayer2.extractor.ExtractorsFactory
;
import
com.google.android.exoplayer2.extractor.ExtractorsFactory
;
import
com.google.android.exoplayer2.extractor.GaplessInfo
;
import
com.google.android.exoplayer2.extractor.GaplessInfoHolder
;
import
com.google.android.exoplayer2.extractor.GaplessInfoHolder
;
import
com.google.android.exoplayer2.extractor.PositionHolder
;
import
com.google.android.exoplayer2.extractor.PositionHolder
;
import
com.google.android.exoplayer2.extractor.SeekMap
;
import
com.google.android.exoplayer2.extractor.SeekMap
;
import
com.google.android.exoplayer2.extractor.TrackOutput
;
import
com.google.android.exoplayer2.extractor.TrackOutput
;
import
com.google.android.exoplayer2.extractor.mp4.Atom.ContainerAtom
;
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.Assertions
;
import
com.google.android.exoplayer2.util.NalUnitUtil
;
import
com.google.android.exoplayer2.util.NalUnitUtil
;
import
com.google.android.exoplayer2.util.ParsableByteArray
;
import
com.google.android.exoplayer2.util.ParsableByteArray
;
...
@@ -309,11 +311,16 @@ public final class Mp4Extractor implements Extractor, SeekMap {
...
@@ -309,11 +311,16 @@ public final class Mp4Extractor implements Extractor, SeekMap {
long
durationUs
=
C
.
TIME_UNSET
;
long
durationUs
=
C
.
TIME_UNSET
;
List
<
Mp4Track
>
tracks
=
new
ArrayList
<>();
List
<
Mp4Track
>
tracks
=
new
ArrayList
<>();
long
earliestSampleOffset
=
Long
.
MAX_VALUE
;
long
earliestSampleOffset
=
Long
.
MAX_VALUE
;
GaplessInfo
gaplessInfo
=
null
;
Metadata
metadata
=
null
;
GaplessInfoHolder
gaplessInfoHolder
=
new
GaplessInfoHolder
();
Atom
.
LeafAtom
udta
=
moov
.
getLeafAtomOfType
(
Atom
.
TYPE_udta
);
Atom
.
LeafAtom
udta
=
moov
.
getLeafAtomOfType
(
Atom
.
TYPE_udta
);
if
(
udta
!=
null
)
{
if
(
udta
!=
null
)
{
AtomParsers
.
parseUdta
(
udta
,
isQuickTime
,
gaplessInfoHolder
);
Metadata
info
=
AtomParsers
.
parseUdta
(
udta
,
isQuickTime
);
if
(
info
!=
null
)
{
gaplessInfo
=
info
.
getGaplessInfo
();
metadata
=
info
;
}
}
}
for
(
int
i
=
0
;
i
<
moov
.
containerChildren
.
size
();
i
++)
{
for
(
int
i
=
0
;
i
<
moov
.
containerChildren
.
size
();
i
++)
{
...
@@ -330,7 +337,10 @@ public final class Mp4Extractor implements Extractor, SeekMap {
...
@@ -330,7 +337,10 @@ public final class Mp4Extractor implements Extractor, SeekMap {
Atom
.
ContainerAtom
stblAtom
=
atom
.
getContainerAtomOfType
(
Atom
.
TYPE_mdia
)
Atom
.
ContainerAtom
stblAtom
=
atom
.
getContainerAtomOfType
(
Atom
.
TYPE_mdia
)
.
getContainerAtomOfType
(
Atom
.
TYPE_minf
).
getContainerAtomOfType
(
Atom
.
TYPE_stbl
);
.
getContainerAtomOfType
(
Atom
.
TYPE_minf
).
getContainerAtomOfType
(
Atom
.
TYPE_stbl
);
GaplessInfoHolder
gaplessInfoHolder
=
new
GaplessInfoHolder
();
gaplessInfoHolder
.
gaplessInfo
=
gaplessInfo
;
TrackSampleTable
trackSampleTable
=
AtomParsers
.
parseStbl
(
track
,
stblAtom
,
gaplessInfoHolder
);
TrackSampleTable
trackSampleTable
=
AtomParsers
.
parseStbl
(
track
,
stblAtom
,
gaplessInfoHolder
);
gaplessInfo
=
gaplessInfoHolder
.
gaplessInfo
;
if
(
trackSampleTable
.
sampleCount
==
0
)
{
if
(
trackSampleTable
.
sampleCount
==
0
)
{
continue
;
continue
;
}
}
...
@@ -340,9 +350,11 @@ public final class Mp4Extractor implements Extractor, SeekMap {
...
@@ -340,9 +350,11 @@ public final class Mp4Extractor implements Extractor, SeekMap {
// Allow ten source samples per output sample, like the platform extractor.
// Allow ten source samples per output sample, like the platform extractor.
int
maxInputSize
=
trackSampleTable
.
maximumSize
+
3
*
10
;
int
maxInputSize
=
trackSampleTable
.
maximumSize
+
3
*
10
;
Format
format
=
track
.
format
.
copyWithMaxInputSize
(
maxInputSize
);
Format
format
=
track
.
format
.
copyWithMaxInputSize
(
maxInputSize
);
if
(
track
.
type
==
C
.
TRACK_TYPE_AUDIO
&&
gaplessInfoHolder
.
hasGaplessInfo
())
{
if
(
track
.
type
==
C
.
TRACK_TYPE_AUDIO
&&
gaplessInfo
!=
null
)
{
format
=
format
.
copyWithGaplessInfo
(
gaplessInfoHolder
.
encoderDelay
,
format
=
format
.
copyWithGaplessInfo
(
gaplessInfo
.
encoderDelay
,
gaplessInfo
.
encoderPadding
);
gaplessInfoHolder
.
encoderPadding
);
}
if
(
metadata
!=
null
)
{
format
=
format
.
copyWithMetadata
(
metadata
);
}
}
mp4Track
.
trackOutput
.
format
(
format
);
mp4Track
.
trackOutput
.
format
(
format
);
...
...
library/src/main/java/com/google/android/exoplayer2/metadata/Metadata.java
0 → 100644
View file @
18ab9634
/*
* 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
com.google.android.exoplayer2.extractor.GaplessInfo
;
import
com.google.android.exoplayer2.metadata.id3.Id3Frame
;
import
java.util.ArrayList
;
import
java.util.Arrays
;
import
java.util.Collections
;
import
java.util.List
;
/**
* ID3 style metadata, with convenient access to gapless playback information.
*/
public
class
Metadata
implements
Parcelable
{
private
final
List
<
Id3Frame
>
frames
;
private
final
GaplessInfo
gaplessInfo
;
public
Metadata
(
List
<
Id3Frame
>
frames
,
GaplessInfo
gaplessInfo
)
{
List
<
Id3Frame
>
theFrames
=
frames
!=
null
?
new
ArrayList
<>(
frames
)
:
new
ArrayList
<
Id3Frame
>();
this
.
frames
=
Collections
.
unmodifiableList
(
theFrames
);
this
.
gaplessInfo
=
gaplessInfo
;
}
public
Metadata
(
Parcel
in
)
{
int
encoderDelay
=
in
.
readInt
();
int
encoderPadding
=
in
.
readInt
();
gaplessInfo
=
encoderDelay
>
0
||
encoderPadding
>
0
?
new
GaplessInfo
(
encoderDelay
,
encoderPadding
)
:
null
;
frames
=
Arrays
.
asList
((
Id3Frame
[])
in
.
readArray
(
Id3Frame
.
class
.
getClassLoader
()));
}
public
Metadata
withGaplessInfo
(
GaplessInfo
info
)
{
return
new
Metadata
(
frames
,
info
);
}
public
List
<
Id3Frame
>
getFrames
()
{
return
frames
;
}
public
GaplessInfo
getGaplessInfo
()
{
return
gaplessInfo
;
}
@Override
public
boolean
equals
(
Object
o
)
{
if
(
this
==
o
)
return
true
;
if
(
o
==
null
||
getClass
()
!=
o
.
getClass
())
return
false
;
Metadata
that
=
(
Metadata
)
o
;
if
(!
frames
.
equals
(
that
.
frames
))
return
false
;
return
gaplessInfo
!=
null
?
gaplessInfo
.
equals
(
that
.
gaplessInfo
)
:
that
.
gaplessInfo
==
null
;
}
@Override
public
int
hashCode
()
{
int
result
=
frames
.
hashCode
();
result
=
31
*
result
+
(
gaplessInfo
!=
null
?
gaplessInfo
.
hashCode
()
:
0
);
return
result
;
}
@Override
public
int
describeContents
()
{
return
0
;
}
@Override
public
void
writeToParcel
(
Parcel
dest
,
int
flags
)
{
dest
.
writeInt
(
gaplessInfo
!=
null
?
gaplessInfo
.
encoderDelay
:
-
1
);
dest
.
writeInt
(
gaplessInfo
!=
null
?
gaplessInfo
.
encoderPadding
:
-
1
);
dest
.
writeArray
(
frames
.
toArray
(
new
Id3Frame
[
frames
.
size
()]));
}
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/MetadataBuilder.java
0 → 100644
View file @
18ab9634
/*
* 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
com.google.android.exoplayer2.extractor.GaplessInfo
;
import
com.google.android.exoplayer2.metadata.id3.Id3Frame
;
import
java.util.ArrayList
;
import
java.util.List
;
/**
* Builder for ID3 style metadata.
*/
public
class
MetadataBuilder
{
private
List
<
Id3Frame
>
frames
=
new
ArrayList
<>();
private
GaplessInfo
gaplessInfo
;
public
void
add
(
Id3Frame
frame
)
{
frames
.
add
(
frame
);
}
public
void
setGaplessInfo
(
GaplessInfo
info
)
{
this
.
gaplessInfo
=
info
;
}
public
Metadata
build
()
{
return
!
frames
.
isEmpty
()
||
gaplessInfo
!=
null
?
new
Metadata
(
frames
,
gaplessInfo
):
null
;
}
}
library/src/main/java/com/google/android/exoplayer2/metadata/id3/ApicFrame.java
View file @
18ab9634
...
@@ -15,6 +15,10 @@
...
@@ -15,6 +15,10 @@
*/
*/
package
com
.
google
.
android
.
exoplayer2
.
metadata
.
id3
;
package
com
.
google
.
android
.
exoplayer2
.
metadata
.
id3
;
import
android.os.Parcel
;
import
android.os.Parcelable
;
import
java.util.Arrays
;
/**
/**
* APIC (Attached Picture) ID3 frame.
* APIC (Attached Picture) ID3 frame.
*/
*/
...
@@ -35,4 +39,62 @@ public final class ApicFrame extends Id3Frame {
...
@@ -35,4 +39,62 @@ public final class ApicFrame extends Id3Frame {
this
.
pictureData
=
pictureData
;
this
.
pictureData
=
pictureData
;
}
}
public
ApicFrame
(
Parcel
in
)
{
super
(
in
);
mimeType
=
in
.
readString
();
description
=
in
.
readString
();
pictureType
=
in
.
readInt
();
pictureData
=
in
.
createByteArray
();
}
@Override
public
boolean
equals
(
Object
o
)
{
if
(
this
==
o
)
return
true
;
if
(
o
==
null
||
getClass
()
!=
o
.
getClass
())
return
false
;
ApicFrame
that
=
(
ApicFrame
)
o
;
if
(
id
!=
null
?
!
id
.
equals
(
that
.
id
)
:
that
.
id
!=
null
)
return
false
;
if
(
pictureType
!=
that
.
pictureType
)
return
false
;
if
(
mimeType
!=
null
?
!
mimeType
.
equals
(
that
.
mimeType
)
:
that
.
mimeType
!=
null
)
return
false
;
if
(
description
!=
null
?
!
description
.
equals
(
that
.
description
)
:
that
.
description
!=
null
)
return
false
;
return
Arrays
.
equals
(
pictureData
,
that
.
pictureData
);
}
@Override
public
int
hashCode
()
{
int
result
=
id
!=
null
?
id
.
hashCode
()
:
0
;
result
=
31
*
result
+
(
mimeType
!=
null
?
mimeType
.
hashCode
()
:
0
);
result
=
31
*
result
+
(
description
!=
null
?
description
.
hashCode
()
:
0
);
result
=
31
*
result
+
pictureType
;
result
=
31
*
result
+
Arrays
.
hashCode
(
pictureData
);
return
result
;
}
@Override
public
void
writeToParcel
(
Parcel
dest
,
int
flags
)
{
dest
.
writeString
(
id
);
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 @
18ab9634
...
@@ -15,6 +15,10 @@
...
@@ -15,6 +15,10 @@
*/
*/
package
com
.
google
.
android
.
exoplayer2
.
metadata
.
id3
;
package
com
.
google
.
android
.
exoplayer2
.
metadata
.
id3
;
import
android.os.Parcel
;
import
android.os.Parcelable
;
import
java.util.Arrays
;
/**
/**
* Binary ID3 frame.
* Binary ID3 frame.
*/
*/
...
@@ -27,4 +31,49 @@ public final class BinaryFrame extends Id3Frame {
...
@@ -27,4 +31,49 @@ public final class BinaryFrame extends Id3Frame {
this
.
data
=
data
;
this
.
data
=
data
;
}
}
public
BinaryFrame
(
Parcel
in
)
{
super
(
in
);
data
=
in
.
createByteArray
();
}
@Override
public
boolean
equals
(
Object
o
)
{
if
(
this
==
o
)
return
true
;
if
(
o
==
null
||
getClass
()
!=
o
.
getClass
())
return
false
;
BinaryFrame
that
=
(
BinaryFrame
)
o
;
if
(
id
!=
null
?
!
id
.
equals
(
that
.
id
)
:
that
.
id
!=
null
)
return
false
;
return
Arrays
.
equals
(
data
,
that
.
data
);
}
@Override
public
int
hashCode
()
{
int
result
=
id
!=
null
?
id
.
hashCode
()
:
0
;
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 @
18ab9634
/*
* 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
;
/**
* Comment ID3 frame.
*/
public
final
class
CommentFrame
extends
Id3Frame
{
public
final
String
language
;
public
final
String
text
;
public
CommentFrame
(
String
language
,
String
description
,
String
text
)
{
super
(
description
);
this
.
language
=
language
;
this
.
text
=
text
;
}
public
CommentFrame
(
Parcel
in
)
{
super
(
in
);
language
=
in
.
readString
();
text
=
in
.
readString
();
}
@Override
public
boolean
equals
(
Object
o
)
{
if
(
this
==
o
)
return
true
;
if
(
o
==
null
||
getClass
()
!=
o
.
getClass
())
return
false
;
CommentFrame
that
=
(
CommentFrame
)
o
;
if
(
id
!=
null
?
!
id
.
equals
(
that
.
id
)
:
that
.
id
!=
null
)
return
false
;
if
(
language
!=
null
?
!
language
.
equals
(
that
.
language
)
:
that
.
language
!=
null
)
return
false
;
return
text
!=
null
?
text
.
equals
(
that
.
text
)
:
that
.
text
==
null
;
}
@Override
public
int
hashCode
()
{
int
result
=
id
!=
null
?
id
.
hashCode
()
:
0
;
result
=
31
*
result
+
(
language
!=
null
?
language
.
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 @
18ab9634
...
@@ -15,6 +15,10 @@
...
@@ -15,6 +15,10 @@
*/
*/
package
com
.
google
.
android
.
exoplayer2
.
metadata
.
id3
;
package
com
.
google
.
android
.
exoplayer2
.
metadata
.
id3
;
import
android.os.Parcel
;
import
android.os.Parcelable
;
import
java.util.Arrays
;
/**
/**
* GEOB (General Encapsulated Object) ID3 frame.
* GEOB (General Encapsulated Object) ID3 frame.
*/
*/
...
@@ -35,4 +39,63 @@ public final class GeobFrame extends Id3Frame {
...
@@ -35,4 +39,63 @@ public final class GeobFrame extends Id3Frame {
this
.
data
=
data
;
this
.
data
=
data
;
}
}
public
GeobFrame
(
Parcel
in
)
{
super
(
in
);
mimeType
=
in
.
readString
();
filename
=
in
.
readString
();
description
=
in
.
readString
();
data
=
in
.
createByteArray
();
}
@Override
public
boolean
equals
(
Object
o
)
{
if
(
this
==
o
)
return
true
;
if
(
o
==
null
||
getClass
()
!=
o
.
getClass
())
return
false
;
GeobFrame
that
=
(
GeobFrame
)
o
;
if
(
id
!=
null
?
!
id
.
equals
(
that
.
id
)
:
that
.
id
!=
null
)
return
false
;
if
(
mimeType
!=
null
?
!
mimeType
.
equals
(
that
.
mimeType
)
:
that
.
mimeType
!=
null
)
return
false
;
if
(
filename
!=
null
?
!
filename
.
equals
(
that
.
filename
)
:
that
.
filename
!=
null
)
return
false
;
if
(
description
!=
null
?
!
description
.
equals
(
that
.
description
)
:
that
.
description
!=
null
)
return
false
;
return
Arrays
.
equals
(
data
,
that
.
data
);
}
@Override
public
int
hashCode
()
{
int
result
=
id
!=
null
?
id
.
hashCode
()
:
0
;
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
(
id
);
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 @
18ab9634
...
@@ -16,6 +16,8 @@
...
@@ -16,6 +16,8 @@
package
com
.
google
.
android
.
exoplayer2
.
metadata
.
id3
;
package
com
.
google
.
android
.
exoplayer2
.
metadata
.
id3
;
import
com.google.android.exoplayer2.ParserException
;
import
com.google.android.exoplayer2.ParserException
;
import
com.google.android.exoplayer2.extractor.GaplessInfo
;
import
com.google.android.exoplayer2.metadata.Metadata
;
import
com.google.android.exoplayer2.metadata.MetadataDecoder
;
import
com.google.android.exoplayer2.metadata.MetadataDecoder
;
import
com.google.android.exoplayer2.metadata.MetadataDecoderException
;
import
com.google.android.exoplayer2.metadata.MetadataDecoderException
;
import
com.google.android.exoplayer2.util.MimeTypes
;
import
com.google.android.exoplayer2.util.MimeTypes
;
...
@@ -23,69 +25,141 @@ import com.google.android.exoplayer2.util.ParsableByteArray;
...
@@ -23,69 +25,141 @@ import com.google.android.exoplayer2.util.ParsableByteArray;
import
java.io.UnsupportedEncodingException
;
import
java.io.UnsupportedEncodingException
;
import
java.util.ArrayList
;
import
java.util.ArrayList
;
import
java.util.Arrays
;
import
java.util.Arrays
;
import
java.util.Collections
;
import
java.util.List
;
import
java.util.List
;
import
java.util.Locale
;
import
java.util.Locale
;
/**
/**
* Decodes individual TXXX text frames from raw ID3 data.
* Decodes individual TXXX text frames from raw ID3 data.
*/
*/
public
final
class
Id3Decoder
implements
MetadataDecoder
<
List
<
Id3Frame
>
>
{
public
final
class
Id3Decoder
implements
MetadataDecoder
<
Metadata
>
{
private
static
final
int
ID3_TEXT_ENCODING_ISO_8859_1
=
0
;
private
static
final
int
ID3_TEXT_ENCODING_ISO_8859_1
=
0
;
private
static
final
int
ID3_TEXT_ENCODING_UTF_16
=
1
;
private
static
final
int
ID3_TEXT_ENCODING_UTF_16
=
1
;
private
static
final
int
ID3_TEXT_ENCODING_UTF_16BE
=
2
;
private
static
final
int
ID3_TEXT_ENCODING_UTF_16BE
=
2
;
private
static
final
int
ID3_TEXT_ENCODING_UTF_8
=
3
;
private
static
final
int
ID3_TEXT_ENCODING_UTF_8
=
3
;
private
int
majorVersion
;
private
int
minorVersion
;
private
boolean
isUnsynchronized
;
private
GaplessInfo
gaplessInfo
;
@Override
@Override
public
boolean
canDecode
(
String
mimeType
)
{
public
boolean
canDecode
(
String
mimeType
)
{
return
mimeType
.
equals
(
MimeTypes
.
APPLICATION_ID3
);
return
mimeType
.
equals
(
MimeTypes
.
APPLICATION_ID3
);
}
}
@Override
@Override
public
List
<
Id3Frame
>
decode
(
byte
[]
data
,
int
size
)
throws
MetadataDecoderException
{
public
Metadata
decode
(
byte
[]
data
,
int
size
)
throws
MetadataDecoderException
{
List
<
Id3Frame
>
id3Frames
=
new
ArrayList
<>();
List
<
Id3Frame
>
id3Frames
=
new
ArrayList
<>();
ParsableByteArray
id3Data
=
new
ParsableByteArray
(
data
,
size
);
ParsableByteArray
id3Data
=
new
ParsableByteArray
(
data
,
size
);
int
id3Size
=
decodeId3Header
(
id3Data
);
int
id3Size
=
decodeId3Header
(
id3Data
);
if
(
isUnsynchronized
)
{
id3Data
=
removeUnsynchronization
(
id3Data
,
id3Size
);
id3Size
=
id3Data
.
bytesLeft
();
}
while
(
id3Size
>
0
)
{
while
(
id3Size
>
0
)
{
int
frameId0
=
id3Data
.
readUnsignedByte
();
int
frameId0
=
id3Data
.
readUnsignedByte
();
int
frameId1
=
id3Data
.
readUnsignedByte
();
int
frameId1
=
id3Data
.
readUnsignedByte
();
int
frameId2
=
id3Data
.
readUnsignedByte
();
int
frameId2
=
id3Data
.
readUnsignedByte
();
int
frameId3
=
id3Data
.
readUnsignedByte
();
int
frameId3
=
majorVersion
>
2
?
id3Data
.
readUnsignedByte
()
:
0
;
int
frameSize
=
id3Data
.
readSynchSafeInt
();
int
frameSize
=
majorVersion
==
2
?
id3Data
.
readUnsignedInt24
()
:
majorVersion
==
3
?
id3Data
.
readInt
()
:
id3Data
.
readSynchSafeInt
();
if
(
frameSize
<=
1
)
{
if
(
frameSize
<=
1
)
{
break
;
break
;
}
}
// Skip frame flags.
// Frame flags.
id3Data
.
skipBytes
(
2
);
boolean
isCompressed
=
false
;
boolean
isEncrypted
=
false
;
try
{
boolean
isUnsynchronized
=
false
;
Id3Frame
frame
;
boolean
hasGroupIdentifier
=
false
;
if
(
frameId0
==
'T'
&&
frameId1
==
'X'
&&
frameId2
==
'X'
&&
frameId3
==
'X'
)
{
boolean
hasDataLength
=
false
;
frame
=
decodeTxxxFrame
(
id3Data
,
frameSize
);
}
else
if
(
frameId0
==
'P'
&&
frameId1
==
'R'
&&
frameId2
==
'I'
&&
frameId3
==
'V'
)
{
if
(
majorVersion
>
2
)
{
frame
=
decodePrivFrame
(
id3Data
,
frameSize
);
int
flags
=
id3Data
.
readShort
();
}
else
if
(
frameId0
==
'G'
&&
frameId1
==
'E'
&&
frameId2
==
'O'
&&
frameId3
==
'B'
)
{
if
(
majorVersion
==
3
)
{
frame
=
decodeGeobFrame
(
id3Data
,
frameSize
);
isCompressed
=
(
flags
&
0x0080
)
!=
0
;
}
else
if
(
frameId0
==
'A'
&&
frameId1
==
'P'
&&
frameId2
==
'I'
&&
frameId3
==
'C'
)
{
isEncrypted
=
(
flags
&
0x0040
)
!=
0
;
frame
=
decodeApicFrame
(
id3Data
,
frameSize
);
hasDataLength
=
isCompressed
;
}
else
if
(
frameId0
==
'T'
)
{
String
id
=
String
.
format
(
Locale
.
US
,
"%c%c%c%c"
,
frameId0
,
frameId1
,
frameId2
,
frameId3
);
frame
=
decodeTextInformationFrame
(
id3Data
,
frameSize
,
id
);
}
else
{
}
else
{
String
id
=
String
.
format
(
Locale
.
US
,
"%c%c%c%c"
,
frameId0
,
frameId1
,
frameId2
,
frameId3
);
isCompressed
=
(
flags
&
0x0008
)
!=
0
;
frame
=
decodeBinaryFrame
(
id3Data
,
frameSize
,
id
);
isEncrypted
=
(
flags
&
0x0004
)
!=
0
;
isUnsynchronized
=
(
flags
&
0x0002
)
!=
0
;
hasGroupIdentifier
=
(
flags
&
0x0040
)
!=
0
;
hasDataLength
=
(
flags
&
0x0001
)
!=
0
;
}
}
int
headerSize
=
majorVersion
==
2
?
6
:
10
;
if
(
hasGroupIdentifier
)
{
++
headerSize
;
--
frameSize
;
id3Data
.
skipBytes
(
1
);
}
if
(
isEncrypted
)
{
++
headerSize
;
--
frameSize
;
id3Data
.
skipBytes
(
1
);
}
if
(
hasDataLength
)
{
headerSize
+=
4
;
frameSize
-=
4
;
id3Data
.
skipBytes
(
4
);
}
id3Size
-=
frameSize
+
headerSize
;
if
(
isCompressed
||
isEncrypted
)
{
id3Data
.
skipBytes
(
frameSize
);
}
else
{
try
{
Id3Frame
frame
;
ParsableByteArray
frameData
=
id3Data
;
if
(
isUnsynchronized
)
{
frameData
=
removeUnsynchronization
(
id3Data
,
frameSize
);
frameSize
=
frameData
.
bytesLeft
();
}
if
(
frameId0
==
'T'
&&
frameId1
==
'X'
&&
frameId2
==
'X'
&&
frameId3
==
'X'
)
{
frame
=
decodeTxxxFrame
(
frameData
,
frameSize
);
}
else
if
(
frameId0
==
'P'
&&
frameId1
==
'R'
&&
frameId2
==
'I'
&&
frameId3
==
'V'
)
{
frame
=
decodePrivFrame
(
frameData
,
frameSize
);
}
else
if
(
frameId0
==
'G'
&&
frameId1
==
'E'
&&
frameId2
==
'O'
&&
frameId3
==
'B'
)
{
frame
=
decodeGeobFrame
(
frameData
,
frameSize
);
}
else
if
(
frameId0
==
'A'
&&
frameId1
==
'P'
&&
frameId2
==
'I'
&&
frameId3
==
'C'
)
{
frame
=
decodeApicFrame
(
frameData
,
frameSize
);
}
else
if
(
frameId0
==
'T'
)
{
String
id
=
frameId3
!=
0
?
String
.
format
(
Locale
.
US
,
"%c%c%c%c"
,
frameId0
,
frameId1
,
frameId2
,
frameId3
)
:
String
.
format
(
Locale
.
US
,
"%c%c%c"
,
frameId0
,
frameId1
,
frameId2
);
frame
=
decodeTextInformationFrame
(
frameData
,
frameSize
,
id
);
}
else
if
(
frameId0
==
'C'
&&
frameId1
==
'O'
&&
frameId2
==
'M'
&&
(
frameId3
==
'M'
||
frameId3
==
0
))
{
CommentFrame
commentFrame
=
decodeCommentFrame
(
frameData
,
frameSize
);
frame
=
commentFrame
;
if
(
gaplessInfo
==
null
)
{
gaplessInfo
=
GaplessInfo
.
createFromComment
(
commentFrame
.
id
,
commentFrame
.
text
);
}
}
else
{
String
id
=
frameId3
!=
0
?
String
.
format
(
Locale
.
US
,
"%c%c%c%c"
,
frameId0
,
frameId1
,
frameId2
,
frameId3
)
:
String
.
format
(
Locale
.
US
,
"%c%c%c"
,
frameId0
,
frameId1
,
frameId2
);
frame
=
decodeBinaryFrame
(
frameData
,
frameSize
,
id
);
}
id3Frames
.
add
(
frame
);
}
catch
(
UnsupportedEncodingException
e
)
{
throw
new
MetadataDecoderException
(
"Unsupported character encoding"
);
}
}
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
,
null
);
}
}
private
static
int
indexOfEos
(
byte
[]
data
,
int
fromIndex
,
int
encoding
)
{
private
static
int
indexOfEos
(
byte
[]
data
,
int
fromIndex
,
int
encoding
)
{
...
@@ -96,7 +170,7 @@ public final class Id3Decoder implements MetadataDecoder<List<Id3Frame>> {
...
@@ -96,7 +170,7 @@ public final class Id3Decoder implements MetadataDecoder<List<Id3Frame>> {
return
terminationPos
;
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
)
{
while
(
terminationPos
<
data
.
length
-
1
)
{
if
(
terminationPos
%
2
==
0
&&
data
[
terminationPos
+
1
]
==
(
byte
)
0
)
{
if
(
terminationPos
%
2
==
0
&&
data
[
terminationPos
+
1
]
==
(
byte
)
0
)
{
return
terminationPos
;
return
terminationPos
;
...
@@ -126,7 +200,7 @@ public final class Id3Decoder implements MetadataDecoder<List<Id3Frame>> {
...
@@ -126,7 +200,7 @@ public final class Id3Decoder implements MetadataDecoder<List<Id3Frame>> {
* @return The size of ID3 frames in bytes, excluding the header and footer.
* @return The size of ID3 frames in bytes, excluding the header and footer.
* @throws ParserException If ID3 file identifier != "ID3".
* @throws ParserException If ID3 file identifier != "ID3".
*/
*/
private
static
int
decodeId3Header
(
ParsableByteArray
id3Buffer
)
throws
MetadataDecoderException
{
private
int
decodeId3Header
(
ParsableByteArray
id3Buffer
)
throws
MetadataDecoderException
{
int
id1
=
id3Buffer
.
readUnsignedByte
();
int
id1
=
id3Buffer
.
readUnsignedByte
();
int
id2
=
id3Buffer
.
readUnsignedByte
();
int
id2
=
id3Buffer
.
readUnsignedByte
();
int
id3
=
id3Buffer
.
readUnsignedByte
();
int
id3
=
id3Buffer
.
readUnsignedByte
();
...
@@ -134,23 +208,41 @@ public final class Id3Decoder implements MetadataDecoder<List<Id3Frame>> {
...
@@ -134,23 +208,41 @@ public final class Id3Decoder implements MetadataDecoder<List<Id3Frame>> {
throw
new
MetadataDecoderException
(
String
.
format
(
Locale
.
US
,
throw
new
MetadataDecoderException
(
String
.
format
(
Locale
.
US
,
"Unexpected ID3 file identifier, expected \"ID3\", actual \"%c%c%c\"."
,
id1
,
id2
,
id3
));
"Unexpected ID3 file identifier, expected \"ID3\", actual \"%c%c%c\"."
,
id1
,
id2
,
id3
));
}
}
id3Buffer
.
skipBytes
(
2
);
// Skip version.
majorVersion
=
id3Buffer
.
readUnsignedByte
();
minorVersion
=
id3Buffer
.
readUnsignedByte
();
int
flags
=
id3Buffer
.
readUnsignedByte
();
int
flags
=
id3Buffer
.
readUnsignedByte
();
int
id3Size
=
id3Buffer
.
readSynchSafeInt
();
int
id3Size
=
id3Buffer
.
readSynchSafeInt
();
// Check if extended header presents.
if
(
majorVersion
<
4
)
{
if
((
flags
&
0x2
)
!=
0
)
{
// this flag is advisory in version 4, use the frame flags instead
int
extendedHeaderSize
=
id3Buffer
.
readSynchSafeInt
();
isUnsynchronized
=
(
flags
&
0x80
)
!=
0
;
if
(
extendedHeaderSize
>
4
)
{
id3Buffer
.
skipBytes
(
extendedHeaderSize
-
4
);
}
id3Size
-=
extendedHeaderSize
;
}
}
// Check if footer presents.
if
(
majorVersion
==
3
)
{
if
((
flags
&
0x8
)
!=
0
)
{
// check for extended header
id3Size
-=
10
;
if
((
flags
&
0x40
)
!=
0
)
{
int
extendedHeaderSize
=
id3Buffer
.
readInt
();
// size excluding size field
if
(
extendedHeaderSize
==
6
||
extendedHeaderSize
==
10
)
{
id3Buffer
.
skipBytes
(
extendedHeaderSize
);
id3Size
-=
(
extendedHeaderSize
+
4
);
}
}
}
else
if
(
majorVersion
>=
4
)
{
// check for extended header
if
((
flags
&
0x40
)
!=
0
)
{
int
extendedHeaderSize
=
id3Buffer
.
readSynchSafeInt
();
// size including size field
if
(
extendedHeaderSize
>
4
)
{
id3Buffer
.
skipBytes
(
extendedHeaderSize
-
4
);
}
id3Size
-=
extendedHeaderSize
;
}
// Check if footer presents.
if
((
flags
&
0x10
)
!=
0
)
{
id3Size
-=
10
;
}
}
}
return
id3Size
;
return
id3Size
;
...
@@ -253,6 +345,28 @@ public final class Id3Decoder implements MetadataDecoder<List<Id3Frame>> {
...
@@ -253,6 +345,28 @@ public final class Id3Decoder implements MetadataDecoder<List<Id3Frame>> {
return
new
TextInformationFrame
(
id
,
description
);
return
new
TextInformationFrame
(
id
,
description
);
}
}
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
valueStartIndex
=
descriptionEndIndex
+
delimiterLength
(
encoding
);
int
valueEndIndex
=
indexOfEos
(
data
,
valueStartIndex
,
encoding
);
String
value
=
new
String
(
data
,
valueStartIndex
,
valueEndIndex
-
valueStartIndex
,
charset
);
return
new
CommentFrame
(
language
,
description
,
value
);
}
private
static
BinaryFrame
decodeBinaryFrame
(
ParsableByteArray
id3Data
,
int
frameSize
,
private
static
BinaryFrame
decodeBinaryFrame
(
ParsableByteArray
id3Data
,
int
frameSize
,
String
id
)
{
String
id
)
{
byte
[]
frame
=
new
byte
[
frameSize
];
byte
[]
frame
=
new
byte
[
frameSize
];
...
@@ -262,6 +376,37 @@ public final class Id3Decoder implements MetadataDecoder<List<Id3Frame>> {
...
@@ -262,6 +376,37 @@ public final class Id3Decoder implements MetadataDecoder<List<Id3Frame>> {
}
}
/**
/**
* Undo the unsynchronization applied to one or more frames.
* @param dataSource The original data, positioned at the beginning of a frame.
* @param count The number of valid bytes in the frames to be processed.
* @return replacement data for the frames.
*/
private
static
ParsableByteArray
removeUnsynchronization
(
ParsableByteArray
dataSource
,
int
count
)
{
byte
[]
source
=
dataSource
.
data
;
int
sourceIndex
=
dataSource
.
getPosition
();
int
limit
=
sourceIndex
+
count
;
byte
[]
dest
=
new
byte
[
count
];
int
destIndex
=
0
;
while
(
sourceIndex
<
limit
)
{
byte
b
=
source
[
sourceIndex
++];
if
((
b
&
0xFF
)
==
0xFF
)
{
int
nextIndex
=
sourceIndex
+
1
;
if
(
nextIndex
<
limit
)
{
int
b2
=
source
[
nextIndex
];
if
(
b2
==
0
)
{
// skip the 0 byte
++
sourceIndex
;
}
}
}
dest
[
destIndex
++]
=
b
;
}
return
new
ParsableByteArray
(
dest
,
destIndex
);
}
/**
* Maps encoding byte from ID3v2 frame to a Charset.
* Maps encoding byte from ID3v2 frame to a Charset.
* @param encodingByte The value of encoding byte from ID3v2 frame.
* @param encodingByte The value of encoding byte from ID3v2 frame.
* @return Charset name.
* @return Charset name.
...
@@ -281,4 +426,52 @@ public final class Id3Decoder implements MetadataDecoder<List<Id3Frame>> {
...
@@ -281,4 +426,52 @@ public final class Id3Decoder implements MetadataDecoder<List<Id3Frame>> {
}
}
}
}
private
final
static
String
[]
standardGenres
=
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 but backported into 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 also invented by the Winamp folks but ignored by the ID3 authors.
"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"
};
public
static
String
decodeGenre
(
int
n
)
{
n
--;
if
(
n
<
0
||
n
>=
standardGenres
.
length
)
{
return
null
;
}
return
standardGenres
[
n
];
}
}
}
library/src/main/java/com/google/android/exoplayer2/metadata/id3/Id3Frame.java
View file @
18ab9634
...
@@ -15,10 +15,13 @@
...
@@ -15,10 +15,13 @@
*/
*/
package
com
.
google
.
android
.
exoplayer2
.
metadata
.
id3
;
package
com
.
google
.
android
.
exoplayer2
.
metadata
.
id3
;
import
android.os.Parcel
;
import
android.os.Parcelable
;
/**
/**
* Base class for ID3 frames.
* Base class for ID3 frames.
*/
*/
public
abstract
class
Id3Frame
{
public
abstract
class
Id3Frame
implements
Parcelable
{
/**
/**
* The frame ID.
* The frame ID.
...
@@ -29,4 +32,13 @@ public abstract class Id3Frame {
...
@@ -29,4 +32,13 @@ public abstract class Id3Frame {
this
.
id
=
id
;
this
.
id
=
id
;
}
}
protected
Id3Frame
(
Parcel
in
)
{
id
=
in
.
readString
();
}
@Override
public
int
describeContents
()
{
return
0
;
}
}
}
library/src/main/java/com/google/android/exoplayer2/metadata/id3/PrivFrame.java
View file @
18ab9634
...
@@ -15,6 +15,10 @@
...
@@ -15,6 +15,10 @@
*/
*/
package
com
.
google
.
android
.
exoplayer2
.
metadata
.
id3
;
package
com
.
google
.
android
.
exoplayer2
.
metadata
.
id3
;
import
android.os.Parcel
;
import
android.os.Parcelable
;
import
java.util.Arrays
;
/**
/**
* PRIV (Private) ID3 frame.
* PRIV (Private) ID3 frame.
*/
*/
...
@@ -31,4 +35,52 @@ public final class PrivFrame extends Id3Frame {
...
@@ -31,4 +35,52 @@ public final class PrivFrame extends Id3Frame {
this
.
privateData
=
privateData
;
this
.
privateData
=
privateData
;
}
}
public
PrivFrame
(
Parcel
in
)
{
super
(
in
);
owner
=
in
.
readString
();
privateData
=
in
.
createByteArray
();
}
@Override
public
boolean
equals
(
Object
o
)
{
if
(
this
==
o
)
return
true
;
if
(
o
==
null
||
getClass
()
!=
o
.
getClass
())
return
false
;
PrivFrame
that
=
(
PrivFrame
)
o
;
if
(
id
!=
null
?
!
id
.
equals
(
that
.
id
)
:
that
.
id
!=
null
)
return
false
;
if
(
owner
!=
null
?
!
owner
.
equals
(
that
.
owner
)
:
that
.
owner
!=
null
)
return
false
;
return
Arrays
.
equals
(
privateData
,
that
.
privateData
);
}
@Override
public
int
hashCode
()
{
int
result
=
id
!=
null
?
id
.
hashCode
()
:
0
;
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
(
id
);
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 @
18ab9634
...
@@ -15,6 +15,9 @@
...
@@ -15,6 +15,9 @@
*/
*/
package
com
.
google
.
android
.
exoplayer2
.
metadata
.
id3
;
package
com
.
google
.
android
.
exoplayer2
.
metadata
.
id3
;
import
android.os.Parcel
;
import
android.os.Parcelable
;
/**
/**
* Text information ("T000" - "TZZZ", excluding "TXXX") ID3 frame.
* Text information ("T000" - "TZZZ", excluding "TXXX") ID3 frame.
*/
*/
...
@@ -27,4 +30,48 @@ public final class TextInformationFrame extends Id3Frame {
...
@@ -27,4 +30,48 @@ public final class TextInformationFrame extends Id3Frame {
this
.
description
=
description
;
this
.
description
=
description
;
}
}
public
TextInformationFrame
(
Parcel
in
)
{
super
(
in
);
description
=
in
.
readString
();
}
@Override
public
boolean
equals
(
Object
o
)
{
if
(
this
==
o
)
return
true
;
if
(
o
==
null
||
getClass
()
!=
o
.
getClass
())
return
false
;
TextInformationFrame
that
=
(
TextInformationFrame
)
o
;
if
(
id
!=
null
?
!
id
.
equals
(
that
.
id
)
:
that
.
id
!=
null
)
return
false
;
return
description
!=
null
?
description
.
equals
(
that
.
description
)
:
that
.
description
==
null
;
}
@Override
public
int
hashCode
()
{
int
result
=
id
!=
null
?
id
.
hashCode
()
:
0
;
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 @
18ab9634
...
@@ -15,6 +15,9 @@
...
@@ -15,6 +15,9 @@
*/
*/
package
com
.
google
.
android
.
exoplayer2
.
metadata
.
id3
;
package
com
.
google
.
android
.
exoplayer2
.
metadata
.
id3
;
import
android.os.Parcel
;
import
android.os.Parcelable
;
/**
/**
* TXXX (User defined text information) ID3 frame.
* TXXX (User defined text information) ID3 frame.
*/
*/
...
@@ -31,4 +34,53 @@ public final class TxxxFrame extends Id3Frame {
...
@@ -31,4 +34,53 @@ public final class TxxxFrame extends Id3Frame {
this
.
value
=
value
;
this
.
value
=
value
;
}
}
public
TxxxFrame
(
Parcel
in
)
{
super
(
in
);
description
=
in
.
readString
();
value
=
in
.
readString
();
}
@Override
public
boolean
equals
(
Object
o
)
{
if
(
this
==
o
)
return
true
;
if
(
o
==
null
||
getClass
()
!=
o
.
getClass
())
return
false
;
TxxxFrame
that
=
(
TxxxFrame
)
o
;
if
(
id
!=
null
?
!
id
.
equals
(
that
.
id
)
:
that
.
id
!=
null
)
return
false
;
if
(
description
!=
null
?
!
description
.
equals
(
that
.
description
)
:
that
.
description
!=
null
)
return
false
;
return
value
!=
null
?
value
.
equals
(
that
.
value
)
:
that
.
value
==
null
;
}
@Override
public
int
hashCode
()
{
int
result
=
id
!=
null
?
id
.
hashCode
()
:
0
;
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
(
id
);
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/upstream/ContentDataSource.java
View file @
18ab9634
...
@@ -136,22 +136,13 @@ public final class ContentDataSource implements DataSource {
...
@@ -136,22 +136,13 @@ public final class ContentDataSource implements DataSource {
@Override
@Override
public
void
close
()
throws
ContentDataSourceException
{
public
void
close
()
throws
ContentDataSourceException
{
uri
=
null
;
uri
=
null
;
try
{
if
(
inputStream
!=
null
)
{
if
(
inputStream
!=
null
)
{
inputStream
.
close
();
}
}
catch
(
IOException
e
)
{
throw
new
ContentDataSourceException
(
e
);
}
finally
{
inputStream
=
null
;
try
{
try
{
if
(
assetFileDescriptor
!=
null
)
{
inputStream
.
close
();
assetFileDescriptor
.
close
();
}
}
catch
(
IOException
e
)
{
}
catch
(
IOException
e
)
{
throw
new
ContentDataSourceException
(
e
);
throw
new
ContentDataSourceException
(
e
);
}
finally
{
}
finally
{
assetFileDescriptor
=
null
;
inputStream
=
null
;
if
(
opened
)
{
if
(
opened
)
{
opened
=
false
;
opened
=
false
;
if
(
listener
!=
null
)
{
if
(
listener
!=
null
)
{
...
@@ -160,6 +151,13 @@ public final class ContentDataSource implements DataSource {
...
@@ -160,6 +151,13 @@ public final class ContentDataSource implements DataSource {
}
}
}
}
}
}
}
if
(
assetFileDescriptor
!=
null
)
{
try
{
assetFileDescriptor
.
close
();
}
catch
(
Exception
e
)
{
}
assetFileDescriptor
=
null
;
}
}
}
}
library/src/main/java/com/google/android/exoplayer2/util/ParsableByteArray.java
View file @
18ab9634
...
@@ -417,6 +417,24 @@ public final class ParsableByteArray {
...
@@ -417,6 +417,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}.
* Reads the next {@code length} bytes as characters in the specified {@link Charset}.
*
*
* @param length The number of bytes to read.
* @param length The number of bytes to read.
...
...
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