Skip to content

Commit

Permalink
Merge pull request #1234 from outlying/MultipleTXXX-ID3-Tags
Browse files Browse the repository at this point in the history
Support multiple TXXX ID3 tags
  • Loading branch information
ojw28 committed Feb 15, 2016
2 parents 51e5696 + b56857c commit 96f39f5
Show file tree
Hide file tree
Showing 10 changed files with 118 additions and 67 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,10 @@
import com.google.android.exoplayer.demo.player.HlsRendererBuilder;
import com.google.android.exoplayer.demo.player.SmoothStreamingRendererBuilder;
import com.google.android.exoplayer.drm.UnsupportedDrmException;
import com.google.android.exoplayer.metadata.GeobMetadata;
import com.google.android.exoplayer.metadata.PrivMetadata;
import com.google.android.exoplayer.metadata.TxxxMetadata;
import com.google.android.exoplayer.metadata.frame.GeobFrame;
import com.google.android.exoplayer.metadata.frame.Id3Frame;
import com.google.android.exoplayer.metadata.frame.PrivFrame;
import com.google.android.exoplayer.metadata.frame.TxxxFrame;
import com.google.android.exoplayer.text.CaptionStyleCompat;
import com.google.android.exoplayer.text.Cue;
import com.google.android.exoplayer.text.SubtitleLayout;
Expand Down Expand Up @@ -74,7 +75,6 @@
import java.net.CookiePolicy;
import java.util.List;
import java.util.Locale;
import java.util.Map;

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

@Override
public void onId3Metadata(Map<String, Object> metadata) {
for (Map.Entry<String, Object> entry : metadata.entrySet()) {
if (TxxxMetadata.TYPE.equals(entry.getKey())) {
TxxxMetadata txxxMetadata = (TxxxMetadata) entry.getValue();
public void onId3Metadata(List<Id3Frame> id3Frames) {
for (Id3Frame id3Frame : id3Frames) {
if (id3Frame instanceof TxxxFrame) {
TxxxFrame txxxFrame = (TxxxFrame) id3Frame;
Log.i(TAG, String.format("ID3 TimedMetadata %s: description=%s, value=%s",
TxxxMetadata.TYPE, txxxMetadata.description, txxxMetadata.value));
} else if (PrivMetadata.TYPE.equals(entry.getKey())) {
PrivMetadata privMetadata = (PrivMetadata) entry.getValue();
id3Frame.frameId, txxxFrame.description, txxxFrame.value));
} else if (id3Frame instanceof PrivFrame) {
PrivFrame privFrame = (PrivFrame) id3Frame;
Log.i(TAG, String.format("ID3 TimedMetadata %s: owner=%s",
PrivMetadata.TYPE, privMetadata.owner));
} else if (GeobMetadata.TYPE.equals(entry.getKey())) {
GeobMetadata geobMetadata = (GeobMetadata) entry.getValue();
id3Frame.frameId, privFrame.owner));
} else if (id3Frame instanceof GeobFrame) {
GeobFrame geobFrame = (GeobFrame) id3Frame;
Log.i(TAG, String.format("ID3 TimedMetadata %s: mimeType=%s, filename=%s, description=%s",
GeobMetadata.TYPE, geobMetadata.mimeType, geobMetadata.filename,
geobMetadata.description));
id3Frame.frameId, geobFrame.mimeType, geobFrame.filename,
geobFrame.description));
} else {
Log.i(TAG, String.format("ID3 TimedMetadata %s", entry.getKey()));
Log.i(TAG, String.format("ID3 TimedMetadata %s", id3Frame.frameId));
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
import com.google.android.exoplayer.drm.StreamingDrmSessionManager;
import com.google.android.exoplayer.hls.HlsSampleSource;
import com.google.android.exoplayer.metadata.MetadataTrackRenderer.MetadataRenderer;
import com.google.android.exoplayer.metadata.frame.Id3Frame;
import com.google.android.exoplayer.text.Cue;
import com.google.android.exoplayer.text.TextRenderer;
import com.google.android.exoplayer.upstream.BandwidthMeter;
Expand Down Expand Up @@ -60,7 +61,7 @@ public class DemoPlayer implements ExoPlayer.Listener, ChunkSampleSource.EventLi
HlsSampleSource.EventListener, DefaultBandwidthMeter.EventListener,
MediaCodecVideoTrackRenderer.EventListener, MediaCodecAudioTrackRenderer.EventListener,
StreamingDrmSessionManager.EventListener, DashChunkSource.EventListener, TextRenderer,
MetadataRenderer<Map<String, Object>>, DebugTextViewHelper.Provider {
MetadataRenderer<List<Id3Frame>>, DebugTextViewHelper.Provider {

/**
* Builds renderers for the player.
Expand Down Expand Up @@ -140,7 +141,7 @@ public interface CaptionListener {
* A listener for receiving ID3 metadata parsed from the media stream.
*/
public interface Id3MetadataListener {
void onId3Metadata(Map<String, Object> metadata);
void onId3Metadata(List<Id3Frame> id3Frames);
}

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

@Override
public void onMetadata(Map<String, Object> metadata) {
public void onMetadata(List<Id3Frame> id3Frames) {
if (id3MetadataListener != null && getSelectedTrack(TYPE_METADATA) != TRACK_DISABLED) {
id3MetadataListener.onId3Metadata(metadata);
id3MetadataListener.onId3Metadata(id3Frames);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
import com.google.android.exoplayer.hls.PtsTimestampAdjusterProvider;
import com.google.android.exoplayer.metadata.Id3Parser;
import com.google.android.exoplayer.metadata.MetadataTrackRenderer;
import com.google.android.exoplayer.metadata.frame.Id3Frame;
import com.google.android.exoplayer.text.TextTrackRenderer;
import com.google.android.exoplayer.text.eia608.Eia608TrackRenderer;
import com.google.android.exoplayer.upstream.DataSource;
Expand All @@ -47,6 +48,7 @@
import android.os.Handler;

import java.io.IOException;
import java.util.List;
import java.util.Map;

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

// Build the text renderer, preferring Webvtt where available.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,32 +15,49 @@
*/
package com.google.android.exoplayer.metadata;

import com.google.android.exoplayer.metadata.frame.Id3Frame;
import com.google.android.exoplayer.metadata.frame.TxxxFrame;

import junit.framework.TestCase;

import java.util.Map;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;

/**
* Test for {@link Id3Parser}
*/
public class Id3ParserTest extends TestCase {

public void testParseTxxxFrames() {
byte[] rawId3 = new byte[] { 73, 68, 51, 4, 0, 0, 0, 0, 0, 41, 84, 88, 88, 88, 0, 0, 0, 31,
byte[] rawId3 = new byte[]{73, 68, 51, 4, 0, 0, 0, 0, 0, 41, 84, 88, 88, 88, 0, 0, 0, 31,
0, 0, 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 };
55, 54, 54, 52, 95, 115, 116, 97, 114, 116, 0};

Id3Parser parser = new Id3Parser();
try {
Map<String, Object> metadata = parser.parse(rawId3, rawId3.length);
assertNotNull(metadata);
assertEquals(1, metadata.size());
TxxxMetadata txxx = (TxxxMetadata) metadata.get(TxxxMetadata.TYPE);
assertNotNull(txxx);
assertEquals("", txxx.description);
assertEquals("mdialog_VINDICO1527664_start", txxx.value);
List<Id3Frame> id3Frames = parser.parse(rawId3, rawId3.length);
assertNotNull(id3Frames);
assertEquals(1, id3Frames.size());
List<TxxxFrame> txxxFrames = ofType(id3Frames, TxxxFrame.class);
assertEquals(1, txxxFrames.size());
TxxxFrame txxxFrame = txxxFrames.toArray(new TxxxFrame[1])[0];
assertNotNull(txxxFrame);
assertEquals("", txxxFrame.description);
assertEquals("mdialog_VINDICO1527664_start", txxxFrame.value);
} catch (Exception exception) {
fail(exception.getMessage());
}
}

private static <T> List<T> ofType(Collection<? super T> col, Class<T> type) {
final List<T> ret = new ArrayList<>();
for (Object o : col) {
if(type.isInstance(o)) {
ret.add((T) o);
}
}
return ret;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -16,19 +16,24 @@
package com.google.android.exoplayer.metadata;

import com.google.android.exoplayer.ParserException;
import com.google.android.exoplayer.metadata.frame.BinaryFrame;
import com.google.android.exoplayer.metadata.frame.GeobFrame;
import com.google.android.exoplayer.metadata.frame.Id3Frame;
import com.google.android.exoplayer.metadata.frame.PrivFrame;
import com.google.android.exoplayer.metadata.frame.TxxxFrame;
import com.google.android.exoplayer.util.MimeTypes;
import com.google.android.exoplayer.util.ParsableByteArray;

import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;

/**
* Extracts individual TXXX text frames from raw ID3 data.
*/
public final class Id3Parser implements MetadataParser<Map<String, Object>> {
public final class Id3Parser implements MetadataParser<List<Id3Frame>> {

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

@Override
public Map<String, Object> parse(byte[] data, int size)
throws UnsupportedEncodingException, ParserException {
Map<String, Object> metadata = new HashMap<>();
public List<Id3Frame> parse( byte[] data, int size)
throws UnsupportedEncodingException, ParserException {
List<Id3Frame> id3Frames = new ArrayList<>();
ParsableByteArray id3Data = new ParsableByteArray(data, size);
int id3Size = parseId3Header(id3Data);

Expand Down Expand Up @@ -71,8 +76,8 @@ public Map<String, Object> parse(byte[] data, int size)
int valueStartIndex = firstZeroIndex + delimiterLength(encoding);
int valueEndIndex = indexOfEOS(frame, valueStartIndex, encoding);
String value = new String(frame, valueStartIndex, valueEndIndex - valueStartIndex,
charset);
metadata.put(TxxxMetadata.TYPE, new TxxxMetadata(description, value));
charset);
id3Frames.add(new TxxxFrame(description, value));
} else if (frameId0 == 'P' && frameId1 == 'R' && frameId2 == 'I' && frameId3 == 'V') {
// Check frame ID == PRIV
byte[] frame = new byte[frameSize];
Expand All @@ -82,7 +87,7 @@ public Map<String, Object> parse(byte[] data, int size)
String owner = new String(frame, 0, firstZeroIndex, "ISO-8859-1");
byte[] privateData = new byte[frameSize - firstZeroIndex - 1];
System.arraycopy(frame, firstZeroIndex + 1, privateData, 0, frameSize - firstZeroIndex - 1);
metadata.put(PrivMetadata.TYPE, new PrivMetadata(owner, privateData));
id3Frames.add(new PrivFrame(owner, privateData));
} else if (frameId0 == 'G' && frameId1 == 'E' && frameId2 == 'O' && frameId3 == 'B') {
// Check frame ID == GEOB
int encoding = id3Data.readUnsignedByte();
Expand All @@ -95,30 +100,29 @@ public Map<String, Object> parse(byte[] data, int size)
int filenameStartIndex = firstZeroIndex + 1;
int filenameEndIndex = indexOfEOS(frame, filenameStartIndex, encoding);
String filename = new String(frame, filenameStartIndex,
filenameEndIndex - filenameStartIndex, charset);
filenameEndIndex - filenameStartIndex, charset);
int descriptionStartIndex = filenameEndIndex + delimiterLength(encoding);
int descriptionEndIndex = indexOfEOS(frame, descriptionStartIndex, encoding);
String description = new String(frame, descriptionStartIndex,
descriptionEndIndex - descriptionStartIndex, charset);
descriptionEndIndex - descriptionStartIndex, charset);

int objectDataSize = frameSize - 1 /* encoding byte */ - descriptionEndIndex
- delimiterLength(encoding);
- delimiterLength(encoding);
byte[] objectData = new byte[objectDataSize];
System.arraycopy(frame, descriptionEndIndex + delimiterLength(encoding), objectData, 0,
objectDataSize);
metadata.put(GeobMetadata.TYPE, new GeobMetadata(mimeType, filename,
description, objectData));
objectDataSize);
id3Frames.add(new GeobFrame(mimeType, filename, description, objectData));
} else {
String type = String.format(Locale.US, "%c%c%c%c", frameId0, frameId1, frameId2, frameId3);
byte[] frame = new byte[frameSize];
id3Data.readBytes(frame, 0, frameSize);
metadata.put(type, frame);
id3Frames.add(new BinaryFrame(type,frame));
}

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

return Collections.unmodifiableMap(metadata);
return Collections.unmodifiableList(id3Frames);
}

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

private static int delimiterLength(int encodingByte) {
return (encodingByte == ID3_TEXT_ENCODING_ISO_8859_1
|| encodingByte == ID3_TEXT_ENCODING_UTF_8) ? 1 : 2;
|| encodingByte == ID3_TEXT_ENCODING_UTF_8) ? 1 : 2;
}

/**
Expand All @@ -167,7 +171,7 @@ private static int parseId3Header(ParsableByteArray id3Buffer) throws ParserExce
int id3 = id3Buffer.readUnsignedByte();
if (id1 != 'I' || id2 != 'D' || id3 != '3') {
throw new ParserException(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.

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package com.google.android.exoplayer.metadata.frame;

/**
* Binary ID3 frame
*/
public class BinaryFrame extends Id3Frame {

public final byte[] data;

public BinaryFrame(String type, byte[] data) {
super(type);
this.data = data;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,26 +13,26 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.android.exoplayer.metadata;
package com.google.android.exoplayer.metadata.frame;

/**
* A metadata that contains parsed ID3 GEOB (General Encapsulated Object) frame data associated
* with time indices.
*/
public final class GeobMetadata {
public final class GeobFrame extends Id3Frame {

public static final String TYPE = "GEOB";
public static final String ID = "GEOB";

public final String mimeType;
public final String filename;
public final String description;
public final byte[] data;

public GeobMetadata(String mimeType, String filename, String description, byte[] data) {
public GeobFrame(String mimeType, String filename, String description, byte[] data) {
super(ID);
this.mimeType = mimeType;
this.filename = filename;
this.description = description;
this.data = data;
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package com.google.android.exoplayer.metadata.frame;

/**
* Base abstract class for ID3 frames
*/
public abstract class Id3Frame {

public final String frameId;

public Id3Frame(String frameId) {
this.frameId = frameId;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,22 +13,22 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.android.exoplayer.metadata;
package com.google.android.exoplayer.metadata.frame;

/**
* A metadata that contains parsed ID3 PRIV (Private) frame data associated
* with time indices.
*/
public final class PrivMetadata {
public final class PrivFrame extends Id3Frame {

public static final String TYPE = "PRIV";
public static final String ID = "PRIV";

public final String owner;
public final byte[] privateData;

public PrivMetadata(String owner, byte[] privateData) {
public PrivFrame(String owner, byte[] privateData) {
super(ID);
this.owner = owner;
this.privateData = privateData;
}

}
Loading

0 comments on commit 96f39f5

Please sign in to comment.