Skip to content

Commit 96f39f5

Browse files
committed
Merge pull request #1234 from outlying/MultipleTXXX-ID3-Tags
Support multiple TXXX ID3 tags
2 parents 51e5696 + b56857c commit 96f39f5

File tree

10 files changed

+118
-67
lines changed

10 files changed

+118
-67
lines changed

demo/src/main/java/com/google/android/exoplayer/demo/PlayerActivity.java

Lines changed: 17 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -30,9 +30,10 @@
3030
import com.google.android.exoplayer.demo.player.HlsRendererBuilder;
3131
import com.google.android.exoplayer.demo.player.SmoothStreamingRendererBuilder;
3232
import com.google.android.exoplayer.drm.UnsupportedDrmException;
33-
import com.google.android.exoplayer.metadata.GeobMetadata;
34-
import com.google.android.exoplayer.metadata.PrivMetadata;
35-
import com.google.android.exoplayer.metadata.TxxxMetadata;
33+
import com.google.android.exoplayer.metadata.frame.GeobFrame;
34+
import com.google.android.exoplayer.metadata.frame.Id3Frame;
35+
import com.google.android.exoplayer.metadata.frame.PrivFrame;
36+
import com.google.android.exoplayer.metadata.frame.TxxxFrame;
3637
import com.google.android.exoplayer.text.CaptionStyleCompat;
3738
import com.google.android.exoplayer.text.Cue;
3839
import com.google.android.exoplayer.text.SubtitleLayout;
@@ -74,7 +75,6 @@
7475
import java.net.CookiePolicy;
7576
import java.util.List;
7677
import java.util.Locale;
77-
import java.util.Map;
7878

7979
/**
8080
* An activity that plays media using {@link DemoPlayer}.
@@ -605,23 +605,23 @@ public void onCues(List<Cue> cues) {
605605
// DemoPlayer.MetadataListener implementation
606606

607607
@Override
608-
public void onId3Metadata(Map<String, Object> metadata) {
609-
for (Map.Entry<String, Object> entry : metadata.entrySet()) {
610-
if (TxxxMetadata.TYPE.equals(entry.getKey())) {
611-
TxxxMetadata txxxMetadata = (TxxxMetadata) entry.getValue();
608+
public void onId3Metadata(List<Id3Frame> id3Frames) {
609+
for (Id3Frame id3Frame : id3Frames) {
610+
if (id3Frame instanceof TxxxFrame) {
611+
TxxxFrame txxxFrame = (TxxxFrame) id3Frame;
612612
Log.i(TAG, String.format("ID3 TimedMetadata %s: description=%s, value=%s",
613-
TxxxMetadata.TYPE, txxxMetadata.description, txxxMetadata.value));
614-
} else if (PrivMetadata.TYPE.equals(entry.getKey())) {
615-
PrivMetadata privMetadata = (PrivMetadata) entry.getValue();
613+
id3Frame.frameId, txxxFrame.description, txxxFrame.value));
614+
} else if (id3Frame instanceof PrivFrame) {
615+
PrivFrame privFrame = (PrivFrame) id3Frame;
616616
Log.i(TAG, String.format("ID3 TimedMetadata %s: owner=%s",
617-
PrivMetadata.TYPE, privMetadata.owner));
618-
} else if (GeobMetadata.TYPE.equals(entry.getKey())) {
619-
GeobMetadata geobMetadata = (GeobMetadata) entry.getValue();
617+
id3Frame.frameId, privFrame.owner));
618+
} else if (id3Frame instanceof GeobFrame) {
619+
GeobFrame geobFrame = (GeobFrame) id3Frame;
620620
Log.i(TAG, String.format("ID3 TimedMetadata %s: mimeType=%s, filename=%s, description=%s",
621-
GeobMetadata.TYPE, geobMetadata.mimeType, geobMetadata.filename,
622-
geobMetadata.description));
621+
id3Frame.frameId, geobFrame.mimeType, geobFrame.filename,
622+
geobFrame.description));
623623
} else {
624-
Log.i(TAG, String.format("ID3 TimedMetadata %s", entry.getKey()));
624+
Log.i(TAG, String.format("ID3 TimedMetadata %s", id3Frame.frameId));
625625
}
626626
}
627627
}

demo/src/main/java/com/google/android/exoplayer/demo/player/DemoPlayer.java

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
import com.google.android.exoplayer.drm.StreamingDrmSessionManager;
3434
import com.google.android.exoplayer.hls.HlsSampleSource;
3535
import com.google.android.exoplayer.metadata.MetadataTrackRenderer.MetadataRenderer;
36+
import com.google.android.exoplayer.metadata.frame.Id3Frame;
3637
import com.google.android.exoplayer.text.Cue;
3738
import com.google.android.exoplayer.text.TextRenderer;
3839
import com.google.android.exoplayer.upstream.BandwidthMeter;
@@ -60,7 +61,7 @@ public class DemoPlayer implements ExoPlayer.Listener, ChunkSampleSource.EventLi
6061
HlsSampleSource.EventListener, DefaultBandwidthMeter.EventListener,
6162
MediaCodecVideoTrackRenderer.EventListener, MediaCodecAudioTrackRenderer.EventListener,
6263
StreamingDrmSessionManager.EventListener, DashChunkSource.EventListener, TextRenderer,
63-
MetadataRenderer<Map<String, Object>>, DebugTextViewHelper.Provider {
64+
MetadataRenderer<List<Id3Frame>>, DebugTextViewHelper.Provider {
6465

6566
/**
6667
* Builds renderers for the player.
@@ -140,7 +141,7 @@ public interface CaptionListener {
140141
* A listener for receiving ID3 metadata parsed from the media stream.
141142
*/
142143
public interface Id3MetadataListener {
143-
void onId3Metadata(Map<String, Object> metadata);
144+
void onId3Metadata(List<Id3Frame> id3Frames);
144145
}
145146

146147
// Constants pulled into this class for convenience.
@@ -519,9 +520,9 @@ public void onCues(List<Cue> cues) {
519520
}
520521

521522
@Override
522-
public void onMetadata(Map<String, Object> metadata) {
523+
public void onMetadata(List<Id3Frame> id3Frames) {
523524
if (id3MetadataListener != null && getSelectedTrack(TYPE_METADATA) != TRACK_DISABLED) {
524-
id3MetadataListener.onId3Metadata(metadata);
525+
id3MetadataListener.onId3Metadata(id3Frames);
525526
}
526527
}
527528

demo/src/main/java/com/google/android/exoplayer/demo/player/HlsRendererBuilder.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
import com.google.android.exoplayer.hls.PtsTimestampAdjusterProvider;
3333
import com.google.android.exoplayer.metadata.Id3Parser;
3434
import com.google.android.exoplayer.metadata.MetadataTrackRenderer;
35+
import com.google.android.exoplayer.metadata.frame.Id3Frame;
3536
import com.google.android.exoplayer.text.TextTrackRenderer;
3637
import com.google.android.exoplayer.text.eia608.Eia608TrackRenderer;
3738
import com.google.android.exoplayer.upstream.DataSource;
@@ -47,6 +48,7 @@
4748
import android.os.Handler;
4849

4950
import java.io.IOException;
51+
import java.util.List;
5052
import java.util.Map;
5153

5254
/**
@@ -145,7 +147,7 @@ public void onSingleManifest(HlsPlaylist manifest) {
145147
MediaCodecAudioTrackRenderer audioRenderer = new MediaCodecAudioTrackRenderer(sampleSource,
146148
MediaCodecSelector.DEFAULT, null, true, player.getMainHandler(), player,
147149
AudioCapabilities.getCapabilities(context), AudioManager.STREAM_MUSIC);
148-
MetadataTrackRenderer<Map<String, Object>> id3Renderer = new MetadataTrackRenderer<>(
150+
MetadataTrackRenderer<List<Id3Frame>> id3Renderer = new MetadataTrackRenderer<>(
149151
sampleSource, new Id3Parser(), player, mainHandler.getLooper());
150152

151153
// Build the text renderer, preferring Webvtt where available.

library/src/androidTest/java/com/google/android/exoplayer/metadata/Id3ParserTest.java

Lines changed: 27 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -15,32 +15,49 @@
1515
*/
1616
package com.google.android.exoplayer.metadata;
1717

18+
import com.google.android.exoplayer.metadata.frame.Id3Frame;
19+
import com.google.android.exoplayer.metadata.frame.TxxxFrame;
20+
1821
import junit.framework.TestCase;
1922

20-
import java.util.Map;
23+
import java.util.ArrayList;
24+
import java.util.Collection;
25+
import java.util.List;
2126

2227
/**
2328
* Test for {@link Id3Parser}
2429
*/
2530
public class Id3ParserTest extends TestCase {
2631

2732
public void testParseTxxxFrames() {
28-
byte[] rawId3 = new byte[] { 73, 68, 51, 4, 0, 0, 0, 0, 0, 41, 84, 88, 88, 88, 0, 0, 0, 31,
33+
byte[] rawId3 = new byte[]{73, 68, 51, 4, 0, 0, 0, 0, 0, 41, 84, 88, 88, 88, 0, 0, 0, 31,
2934
0, 0, 3, 0, 109, 100, 105, 97, 108, 111, 103, 95, 86, 73, 78, 68, 73, 67, 79, 49, 53, 50,
30-
55, 54, 54, 52, 95, 115, 116, 97, 114, 116, 0 };
35+
55, 54, 54, 52, 95, 115, 116, 97, 114, 116, 0};
3136

3237
Id3Parser parser = new Id3Parser();
3338
try {
34-
Map<String, Object> metadata = parser.parse(rawId3, rawId3.length);
35-
assertNotNull(metadata);
36-
assertEquals(1, metadata.size());
37-
TxxxMetadata txxx = (TxxxMetadata) metadata.get(TxxxMetadata.TYPE);
38-
assertNotNull(txxx);
39-
assertEquals("", txxx.description);
40-
assertEquals("mdialog_VINDICO1527664_start", txxx.value);
39+
List<Id3Frame> id3Frames = parser.parse(rawId3, rawId3.length);
40+
assertNotNull(id3Frames);
41+
assertEquals(1, id3Frames.size());
42+
List<TxxxFrame> txxxFrames = ofType(id3Frames, TxxxFrame.class);
43+
assertEquals(1, txxxFrames.size());
44+
TxxxFrame txxxFrame = txxxFrames.toArray(new TxxxFrame[1])[0];
45+
assertNotNull(txxxFrame);
46+
assertEquals("", txxxFrame.description);
47+
assertEquals("mdialog_VINDICO1527664_start", txxxFrame.value);
4148
} catch (Exception exception) {
4249
fail(exception.getMessage());
4350
}
4451
}
4552

53+
private static <T> List<T> ofType(Collection<? super T> col, Class<T> type) {
54+
final List<T> ret = new ArrayList<>();
55+
for (Object o : col) {
56+
if(type.isInstance(o)) {
57+
ret.add((T) o);
58+
}
59+
}
60+
return ret;
61+
}
62+
4663
}

library/src/main/java/com/google/android/exoplayer/metadata/Id3Parser.java

Lines changed: 23 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -16,19 +16,24 @@
1616
package com.google.android.exoplayer.metadata;
1717

1818
import com.google.android.exoplayer.ParserException;
19+
import com.google.android.exoplayer.metadata.frame.BinaryFrame;
20+
import com.google.android.exoplayer.metadata.frame.GeobFrame;
21+
import com.google.android.exoplayer.metadata.frame.Id3Frame;
22+
import com.google.android.exoplayer.metadata.frame.PrivFrame;
23+
import com.google.android.exoplayer.metadata.frame.TxxxFrame;
1924
import com.google.android.exoplayer.util.MimeTypes;
2025
import com.google.android.exoplayer.util.ParsableByteArray;
2126

2227
import java.io.UnsupportedEncodingException;
28+
import java.util.ArrayList;
2329
import java.util.Collections;
24-
import java.util.HashMap;
30+
import java.util.List;
2531
import java.util.Locale;
26-
import java.util.Map;
2732

2833
/**
2934
* Extracts individual TXXX text frames from raw ID3 data.
3035
*/
31-
public final class Id3Parser implements MetadataParser<Map<String, Object>> {
36+
public final class Id3Parser implements MetadataParser<List<Id3Frame>> {
3237

3338
private static final int ID3_TEXT_ENCODING_ISO_8859_1 = 0;
3439
private static final int ID3_TEXT_ENCODING_UTF_16 = 1;
@@ -41,9 +46,9 @@ public boolean canParse(String mimeType) {
4146
}
4247

4348
@Override
44-
public Map<String, Object> parse(byte[] data, int size)
45-
throws UnsupportedEncodingException, ParserException {
46-
Map<String, Object> metadata = new HashMap<>();
49+
public List<Id3Frame> parse( byte[] data, int size)
50+
throws UnsupportedEncodingException, ParserException {
51+
List<Id3Frame> id3Frames = new ArrayList<>();
4752
ParsableByteArray id3Data = new ParsableByteArray(data, size);
4853
int id3Size = parseId3Header(id3Data);
4954

@@ -71,8 +76,8 @@ public Map<String, Object> parse(byte[] data, int size)
7176
int valueStartIndex = firstZeroIndex + delimiterLength(encoding);
7277
int valueEndIndex = indexOfEOS(frame, valueStartIndex, encoding);
7378
String value = new String(frame, valueStartIndex, valueEndIndex - valueStartIndex,
74-
charset);
75-
metadata.put(TxxxMetadata.TYPE, new TxxxMetadata(description, value));
79+
charset);
80+
id3Frames.add(new TxxxFrame(description, value));
7681
} else if (frameId0 == 'P' && frameId1 == 'R' && frameId2 == 'I' && frameId3 == 'V') {
7782
// Check frame ID == PRIV
7883
byte[] frame = new byte[frameSize];
@@ -82,7 +87,7 @@ public Map<String, Object> parse(byte[] data, int size)
8287
String owner = new String(frame, 0, firstZeroIndex, "ISO-8859-1");
8388
byte[] privateData = new byte[frameSize - firstZeroIndex - 1];
8489
System.arraycopy(frame, firstZeroIndex + 1, privateData, 0, frameSize - firstZeroIndex - 1);
85-
metadata.put(PrivMetadata.TYPE, new PrivMetadata(owner, privateData));
90+
id3Frames.add(new PrivFrame(owner, privateData));
8691
} else if (frameId0 == 'G' && frameId1 == 'E' && frameId2 == 'O' && frameId3 == 'B') {
8792
// Check frame ID == GEOB
8893
int encoding = id3Data.readUnsignedByte();
@@ -95,30 +100,29 @@ public Map<String, Object> parse(byte[] data, int size)
95100
int filenameStartIndex = firstZeroIndex + 1;
96101
int filenameEndIndex = indexOfEOS(frame, filenameStartIndex, encoding);
97102
String filename = new String(frame, filenameStartIndex,
98-
filenameEndIndex - filenameStartIndex, charset);
103+
filenameEndIndex - filenameStartIndex, charset);
99104
int descriptionStartIndex = filenameEndIndex + delimiterLength(encoding);
100105
int descriptionEndIndex = indexOfEOS(frame, descriptionStartIndex, encoding);
101106
String description = new String(frame, descriptionStartIndex,
102-
descriptionEndIndex - descriptionStartIndex, charset);
107+
descriptionEndIndex - descriptionStartIndex, charset);
103108

104109
int objectDataSize = frameSize - 1 /* encoding byte */ - descriptionEndIndex
105-
- delimiterLength(encoding);
110+
- delimiterLength(encoding);
106111
byte[] objectData = new byte[objectDataSize];
107112
System.arraycopy(frame, descriptionEndIndex + delimiterLength(encoding), objectData, 0,
108-
objectDataSize);
109-
metadata.put(GeobMetadata.TYPE, new GeobMetadata(mimeType, filename,
110-
description, objectData));
113+
objectDataSize);
114+
id3Frames.add(new GeobFrame(mimeType, filename, description, objectData));
111115
} else {
112116
String type = String.format(Locale.US, "%c%c%c%c", frameId0, frameId1, frameId2, frameId3);
113117
byte[] frame = new byte[frameSize];
114118
id3Data.readBytes(frame, 0, frameSize);
115-
metadata.put(type, frame);
119+
id3Frames.add(new BinaryFrame(type,frame));
116120
}
117121

118122
id3Size -= frameSize + 10 /* header size */;
119123
}
120124

121-
return Collections.unmodifiableMap(metadata);
125+
return Collections.unmodifiableList(id3Frames);
122126
}
123127

124128
private static int indexOf(byte[] data, int fromIndex, byte key) {
@@ -151,7 +155,7 @@ private static int indexOfEOS(byte[] data, int fromIndex, int encodingByte) {
151155

152156
private static int delimiterLength(int encodingByte) {
153157
return (encodingByte == ID3_TEXT_ENCODING_ISO_8859_1
154-
|| encodingByte == ID3_TEXT_ENCODING_UTF_8) ? 1 : 2;
158+
|| encodingByte == ID3_TEXT_ENCODING_UTF_8) ? 1 : 2;
155159
}
156160

157161
/**
@@ -167,7 +171,7 @@ private static int parseId3Header(ParsableByteArray id3Buffer) throws ParserExce
167171
int id3 = id3Buffer.readUnsignedByte();
168172
if (id1 != 'I' || id2 != 'D' || id3 != '3') {
169173
throw new ParserException(String.format(Locale.US,
170-
"Unexpected ID3 file identifier, expected \"ID3\", actual \"%c%c%c\".", id1, id2, id3));
174+
"Unexpected ID3 file identifier, expected \"ID3\", actual \"%c%c%c\".", id1, id2, id3));
171175
}
172176
id3Buffer.skipBytes(2); // Skip version.
173177

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
package com.google.android.exoplayer.metadata.frame;
2+
3+
/**
4+
* Binary ID3 frame
5+
*/
6+
public class BinaryFrame extends Id3Frame {
7+
8+
public final byte[] data;
9+
10+
public BinaryFrame(String type, byte[] data) {
11+
super(type);
12+
this.data = data;
13+
}
14+
}

library/src/main/java/com/google/android/exoplayer/metadata/GeobMetadata.java renamed to library/src/main/java/com/google/android/exoplayer/metadata/frame/GeobFrame.java

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,26 +13,26 @@
1313
* See the License for the specific language governing permissions and
1414
* limitations under the License.
1515
*/
16-
package com.google.android.exoplayer.metadata;
16+
package com.google.android.exoplayer.metadata.frame;
1717

1818
/**
1919
* A metadata that contains parsed ID3 GEOB (General Encapsulated Object) frame data associated
2020
* with time indices.
2121
*/
22-
public final class GeobMetadata {
22+
public final class GeobFrame extends Id3Frame {
2323

24-
public static final String TYPE = "GEOB";
24+
public static final String ID = "GEOB";
2525

2626
public final String mimeType;
2727
public final String filename;
2828
public final String description;
2929
public final byte[] data;
3030

31-
public GeobMetadata(String mimeType, String filename, String description, byte[] data) {
31+
public GeobFrame(String mimeType, String filename, String description, byte[] data) {
32+
super(ID);
3233
this.mimeType = mimeType;
3334
this.filename = filename;
3435
this.description = description;
3536
this.data = data;
3637
}
37-
3838
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
package com.google.android.exoplayer.metadata.frame;
2+
3+
/**
4+
* Base abstract class for ID3 frames
5+
*/
6+
public abstract class Id3Frame {
7+
8+
public final String frameId;
9+
10+
public Id3Frame(String frameId) {
11+
this.frameId = frameId;
12+
}
13+
}

library/src/main/java/com/google/android/exoplayer/metadata/PrivMetadata.java renamed to library/src/main/java/com/google/android/exoplayer/metadata/frame/PrivFrame.java

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,22 +13,22 @@
1313
* See the License for the specific language governing permissions and
1414
* limitations under the License.
1515
*/
16-
package com.google.android.exoplayer.metadata;
16+
package com.google.android.exoplayer.metadata.frame;
1717

1818
/**
1919
* A metadata that contains parsed ID3 PRIV (Private) frame data associated
2020
* with time indices.
2121
*/
22-
public final class PrivMetadata {
22+
public final class PrivFrame extends Id3Frame {
2323

24-
public static final String TYPE = "PRIV";
24+
public static final String ID = "PRIV";
2525

2626
public final String owner;
2727
public final byte[] privateData;
2828

29-
public PrivMetadata(String owner, byte[] privateData) {
29+
public PrivFrame(String owner, byte[] privateData) {
30+
super(ID);
3031
this.owner = owner;
3132
this.privateData = privateData;
3233
}
33-
3434
}

0 commit comments

Comments
 (0)