Commit ced03e9a by ojw28 Committed by GitHub

Merge pull request #2008 from google/dev-v2-id3

Merge ID3 support into dev-v2
parents 2c543632 c4b4e845
Showing with 824 additions and 159 deletions
...@@ -27,8 +27,10 @@ import com.google.android.exoplayer2.Timeline; ...@@ -27,8 +27,10 @@ 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.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;
...@@ -46,7 +48,6 @@ import com.google.android.exoplayer2.upstream.DataSpec; ...@@ -46,7 +48,6 @@ import com.google.android.exoplayer2.upstream.DataSpec;
import com.google.android.exoplayer2.video.VideoRendererEventListener; import com.google.android.exoplayer2.video.VideoRendererEventListener;
import java.io.IOException; import java.io.IOException;
import java.text.NumberFormat; import java.text.NumberFormat;
import java.util.List;
import java.util.Locale; import java.util.Locale;
/** /**
...@@ -55,7 +56,7 @@ import java.util.Locale; ...@@ -55,7 +56,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,
MetadataRenderer.Output<List<Id3Frame>> { MetadataRenderer.Output {
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;
...@@ -157,6 +158,18 @@ import java.util.Locale; ...@@ -157,6 +158,18 @@ import java.util.Locale;
} }
Log.d(TAG, " ]"); Log.d(TAG, " ]");
} }
// Log metadata for at most one of the tracks selected for the renderer.
if (trackSelection != null) {
for (int selectionIndex = 0; selectionIndex < trackSelection.length(); selectionIndex++) {
Metadata metadata = trackSelection.getFormat(selectionIndex).metadata;
if (metadata != null) {
Log.d(TAG, " Metadata [");
printMetadata(metadata, " ");
Log.d(TAG, " ]");
break;
}
}
}
Log.d(TAG, " ]"); Log.d(TAG, " ]");
} }
} }
...@@ -182,34 +195,13 @@ import java.util.Locale; ...@@ -182,34 +195,13 @@ import java.util.Locale;
Log.d(TAG, "]"); Log.d(TAG, "]");
} }
// MetadataRenderer.Output<List<Id3Frame>> // MetadataRenderer.Output
@Override @Override
public void onMetadata(List<Id3Frame> id3Frames) { public void onMetadata(Metadata metadata) {
for (Id3Frame id3Frame : id3Frames) { Log.d(TAG, "onMetadata [");
if (id3Frame instanceof TxxxFrame) { printMetadata(metadata, " ");
TxxxFrame txxxFrame = (TxxxFrame) id3Frame; Log.d(TAG, "]");
Log.i(TAG, String.format("ID3 TimedMetadata %s: description=%s, value=%s", txxxFrame.id,
txxxFrame.description, txxxFrame.value));
} else if (id3Frame instanceof PrivFrame) {
PrivFrame privFrame = (PrivFrame) id3Frame;
Log.i(TAG, String.format("ID3 TimedMetadata %s: owner=%s", privFrame.id, privFrame.owner));
} else if (id3Frame instanceof GeobFrame) {
GeobFrame geobFrame = (GeobFrame) id3Frame;
Log.i(TAG, String.format("ID3 TimedMetadata %s: mimeType=%s, filename=%s, description=%s",
geobFrame.id, geobFrame.mimeType, geobFrame.filename, geobFrame.description));
} else if (id3Frame instanceof ApicFrame) {
ApicFrame apicFrame = (ApicFrame) id3Frame;
Log.i(TAG, String.format("ID3 TimedMetadata %s: mimeType=%s, description=%s",
apicFrame.id, apicFrame.mimeType, apicFrame.description));
} else if (id3Frame instanceof TextInformationFrame) {
TextInformationFrame textInformationFrame = (TextInformationFrame) id3Frame;
Log.i(TAG, String.format("ID3 TimedMetadata %s: description=%s", textInformationFrame.id,
textInformationFrame.description));
} else {
Log.i(TAG, String.format("ID3 TimedMetadata %s", id3Frame.id));
}
}
} }
// AudioRendererEventListener // AudioRendererEventListener
...@@ -354,6 +346,39 @@ import java.util.Locale; ...@@ -354,6 +346,39 @@ import java.util.Locale;
Log.e(TAG, "internalError [" + getSessionTimeString() + ", " + type + "]", e); Log.e(TAG, "internalError [" + getSessionTimeString() + ", " + type + "]", e);
} }
private void printMetadata(Metadata metadata, String prefix) {
for (int i = 0; i < metadata.length(); i++) {
Metadata.Entry entry = metadata.get(i);
if (entry instanceof TxxxFrame) {
TxxxFrame txxxFrame = (TxxxFrame) entry;
Log.d(TAG, prefix + String.format("%s: description=%s, value=%s", txxxFrame.id,
txxxFrame.description, txxxFrame.value));
} else if (entry instanceof PrivFrame) {
PrivFrame privFrame = (PrivFrame) entry;
Log.d(TAG, prefix + String.format("%s: owner=%s", privFrame.id, privFrame.owner));
} else if (entry instanceof GeobFrame) {
GeobFrame geobFrame = (GeobFrame) entry;
Log.d(TAG, prefix + String.format("%s: mimeType=%s, filename=%s, description=%s",
geobFrame.id, geobFrame.mimeType, geobFrame.filename, geobFrame.description));
} else if (entry instanceof ApicFrame) {
ApicFrame apicFrame = (ApicFrame) entry;
Log.d(TAG, prefix + String.format("%s: mimeType=%s, description=%s",
apicFrame.id, apicFrame.mimeType, apicFrame.description));
} else if (entry instanceof TextInformationFrame) {
TextInformationFrame textInformationFrame = (TextInformationFrame) entry;
Log.d(TAG, prefix + String.format("%s: description=%s", textInformationFrame.id,
textInformationFrame.description));
} else if (entry instanceof CommentFrame) {
CommentFrame commentFrame = (CommentFrame) entry;
Log.d(TAG, prefix + String.format("%s: language=%s description=%s", commentFrame.id,
commentFrame.language, commentFrame.description, commentFrame.text));
} else if (entry instanceof Id3Frame) {
Id3Frame id3Frame = (Id3Frame) entry;
Log.d(TAG, prefix + String.format("%s", id3Frame.id));
}
}
}
private String getSessionTimeString() { private String getSessionTimeString() {
return getTimeString(SystemClock.elapsedRealtime() - startTimeMs); return getTimeString(SystemClock.elapsedRealtime() - startTimeMs);
} }
......
...@@ -24,6 +24,8 @@ import android.annotation.TargetApi; ...@@ -24,6 +24,8 @@ import android.annotation.TargetApi;
import android.media.MediaFormat; import android.media.MediaFormat;
import android.os.Parcel; import android.os.Parcel;
import com.google.android.exoplayer2.drm.DrmInitData; import com.google.android.exoplayer2.drm.DrmInitData;
import com.google.android.exoplayer2.metadata.Metadata;
import com.google.android.exoplayer2.metadata.id3.TextInformationFrame;
import com.google.android.exoplayer2.testutil.TestUtil; import com.google.android.exoplayer2.testutil.TestUtil;
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;
...@@ -56,11 +58,14 @@ public final class FormatTest extends TestCase { ...@@ -56,11 +58,14 @@ public final class FormatTest extends TestCase {
TestUtil.buildTestData(128, 1 /* data seed */)); TestUtil.buildTestData(128, 1 /* data seed */));
DrmInitData drmInitData = new DrmInitData(DRM_DATA_1, DRM_DATA_2); DrmInitData drmInitData = new DrmInitData(DRM_DATA_1, DRM_DATA_2);
byte[] projectionData = new byte[] {1, 2, 3}; byte[] projectionData = new byte[] {1, 2, 3};
Metadata metadata = new Metadata(
new TextInformationFrame("id1", "description1"),
new TextInformationFrame("id2", "description2"));
Format formatToParcel = new Format("id", MimeTypes.VIDEO_MP4, MimeTypes.VIDEO_H264, null, Format formatToParcel = new Format("id", MimeTypes.VIDEO_MP4, MimeTypes.VIDEO_H264, null,
1024, 2048, 1920, 1080, 24, 90, 2, projectionData, C.STEREO_MODE_TOP_BOTTOM, 6, 44100, 1024, 2048, 1920, 1080, 24, 90, 2, projectionData, C.STEREO_MODE_TOP_BOTTOM, 6, 44100,
C.ENCODING_PCM_24BIT, 1001, 1002, 0, "und", Format.OFFSET_SAMPLE_RELATIVE, INIT_DATA, C.ENCODING_PCM_24BIT, 1001, 1002, 0, "und", Format.OFFSET_SAMPLE_RELATIVE, INIT_DATA,
drmInitData); drmInitData, metadata);
Parcel parcel = Parcel.obtain(); Parcel parcel = Parcel.obtain();
formatToParcel.writeToParcel(parcel, 0); formatToParcel.writeToParcel(parcel, 0);
......
...@@ -16,8 +16,8 @@ ...@@ -16,8 +16,8 @@
package com.google.android.exoplayer2.metadata.id3; package com.google.android.exoplayer2.metadata.id3;
import android.test.MoreAsserts; import android.test.MoreAsserts;
import com.google.android.exoplayer2.metadata.Metadata;
import com.google.android.exoplayer2.metadata.MetadataDecoderException; import com.google.android.exoplayer2.metadata.MetadataDecoderException;
import java.util.List;
import junit.framework.TestCase; import junit.framework.TestCase;
/** /**
...@@ -30,9 +30,9 @@ public class Id3DecoderTest extends TestCase { ...@@ -30,9 +30,9 @@ public class Id3DecoderTest extends TestCase {
3, 0, 109, 100, 105, 97, 108, 111, 103, 95, 86, 73, 78, 68, 73, 67, 79, 49, 53, 50, 55, 54, 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);
assertEquals(1, id3Frames.size()); assertEquals(1, metadata.length());
TxxxFrame txxxFrame = (TxxxFrame) id3Frames.get(0); TxxxFrame txxxFrame = (TxxxFrame) metadata.get(0);
assertEquals("", txxxFrame.description); assertEquals("", txxxFrame.description);
assertEquals("mdialog_VINDICO1527664_start", txxxFrame.value); assertEquals("mdialog_VINDICO1527664_start", txxxFrame.value);
} }
...@@ -42,9 +42,9 @@ public class Id3DecoderTest extends TestCase { ...@@ -42,9 +42,9 @@ public class Id3DecoderTest extends TestCase {
3, 105, 109, 97, 103, 101, 47, 106, 112, 101, 103, 0, 16, 72, 101, 108, 108, 111, 32, 87, 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);
assertEquals(1, id3Frames.size()); assertEquals(1, metadata.length());
ApicFrame apicFrame = (ApicFrame) id3Frames.get(0); ApicFrame apicFrame = (ApicFrame) metadata.get(0);
assertEquals("image/jpeg", apicFrame.mimeType); assertEquals("image/jpeg", apicFrame.mimeType);
assertEquals(16, apicFrame.pictureType); assertEquals(16, apicFrame.pictureType);
assertEquals("Hello World", apicFrame.description); assertEquals("Hello World", apicFrame.description);
...@@ -56,9 +56,9 @@ public class Id3DecoderTest extends TestCase { ...@@ -56,9 +56,9 @@ public class Id3DecoderTest extends TestCase {
byte[] rawId3 = new byte[] {73, 68, 51, 4, 0, 0, 0, 0, 0, 23, 84, 73, 84, 50, 0, 0, 0, 13, 0, 0, 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);
assertEquals(1, id3Frames.size()); assertEquals(1, metadata.length());
TextInformationFrame textInformationFrame = (TextInformationFrame) id3Frames.get(0); TextInformationFrame textInformationFrame = (TextInformationFrame) metadata.get(0);
assertEquals("TIT2", textInformationFrame.id); assertEquals("TIT2", textInformationFrame.id);
assertEquals("Hello World", textInformationFrame.description); assertEquals("Hello World", textInformationFrame.description);
} }
......
...@@ -35,9 +35,9 @@ import com.google.android.exoplayer2.decoder.DecoderCounters; ...@@ -35,9 +35,9 @@ 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.drm.FrameworkMediaCrypto; import com.google.android.exoplayer2.drm.FrameworkMediaCrypto;
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.source.TrackGroupArray; import com.google.android.exoplayer2.source.TrackGroupArray;
import com.google.android.exoplayer2.text.Cue; import com.google.android.exoplayer2.text.Cue;
...@@ -107,7 +107,7 @@ public final class SimpleExoPlayer implements ExoPlayer { ...@@ -107,7 +107,7 @@ public final class SimpleExoPlayer implements ExoPlayer {
private SurfaceHolder surfaceHolder; private SurfaceHolder surfaceHolder;
private TextureView textureView; private TextureView textureView;
private TextRenderer.Output textOutput; private TextRenderer.Output textOutput;
private MetadataRenderer.Output<List<Id3Frame>> id3Output; private MetadataRenderer.Output metadataOutput;
private VideoListener videoListener; private VideoListener videoListener;
private AudioRendererEventListener audioDebugListener; private AudioRendererEventListener audioDebugListener;
private VideoRendererEventListener videoDebugListener; private VideoRendererEventListener videoDebugListener;
...@@ -364,12 +364,21 @@ public final class SimpleExoPlayer implements ExoPlayer { ...@@ -364,12 +364,21 @@ public final class SimpleExoPlayer implements ExoPlayer {
} }
/** /**
* Sets a listener to receive ID3 metadata events. * @deprecated Use {@link #setMetadataOutput(MetadataRenderer.Output)} instead.
* @param output The output.
*/
@Deprecated
public void setId3Output(MetadataRenderer.Output output) {
setMetadataOutput(output);
}
/**
* Sets a listener to receive metadata events.
* *
* @param output The output. * @param output The output.
*/ */
public void setId3Output(MetadataRenderer.Output<List<Id3Frame>> output) { public void setMetadataOutput(MetadataRenderer.Output output) {
id3Output = output; metadataOutput = output;
} }
// ExoPlayer implementation // ExoPlayer implementation
...@@ -540,9 +549,9 @@ public final class SimpleExoPlayer implements ExoPlayer { ...@@ -540,9 +549,9 @@ 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 metadataRenderer = new MetadataRenderer(componentListener,
mainHandler.getLooper(), new Id3Decoder()); mainHandler.getLooper(), new Id3Decoder());
renderersList.add(id3Renderer); renderersList.add(metadataRenderer);
} }
private void buildExtensionRenderers(ArrayList<Renderer> renderersList, private void buildExtensionRenderers(ArrayList<Renderer> renderersList,
...@@ -644,7 +653,7 @@ public final class SimpleExoPlayer implements ExoPlayer { ...@@ -644,7 +653,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,
SurfaceHolder.Callback, TextureView.SurfaceTextureListener { SurfaceHolder.Callback, TextureView.SurfaceTextureListener {
// VideoRendererEventListener implementation // VideoRendererEventListener implementation
...@@ -775,12 +784,12 @@ public final class SimpleExoPlayer implements ExoPlayer { ...@@ -775,12 +784,12 @@ public final class SimpleExoPlayer implements ExoPlayer {
} }
} }
// MetadataRenderer.Output<List<Id3Frame>> implementation // MetadataRenderer.Output implementation
@Override @Override
public void onMetadata(List<Id3Frame> id3Frames) { public void onMetadata(Metadata metadata) {
if (id3Output != null) { if (metadataOutput != null) {
id3Output.onMetadata(id3Frames); metadataOutput.onMetadata(metadata);
} }
} }
......
...@@ -16,6 +16,8 @@ ...@@ -16,6 +16,8 @@
package com.google.android.exoplayer2.extractor; package com.google.android.exoplayer2.extractor;
import com.google.android.exoplayer2.Format; import com.google.android.exoplayer2.Format;
import com.google.android.exoplayer2.metadata.Metadata;
import com.google.android.exoplayer2.metadata.id3.CommentFrame;
import java.util.regex.Matcher; import java.util.regex.Matcher;
import java.util.regex.Pattern; import java.util.regex.Pattern;
...@@ -66,6 +68,25 @@ public final class GaplessInfoHolder { ...@@ -66,6 +68,25 @@ public final class GaplessInfoHolder {
} }
/** /**
* Populates the holder with data parsed from ID3 {@link Metadata}.
*
* @param metadata The metadata from which to parse the gapless information.
* @return Whether the holder was populated.
*/
public boolean setFromMetadata(Metadata metadata) {
for (int i = 0; i < metadata.length(); i++) {
Metadata.Entry entry = metadata.get(i);
if (entry instanceof CommentFrame) {
CommentFrame commentFrame = (CommentFrame) entry;
if (setFromComment(commentFrame.description, commentFrame.text)) {
return true;
}
}
}
return false;
}
/**
* Populates the holder with data parsed from a gapless playback comment (stored in an ID3 header * 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. * or MPEG 4 user data), if valid and non-zero.
* *
...@@ -73,7 +94,7 @@ public final class GaplessInfoHolder { ...@@ -73,7 +94,7 @@ public final class GaplessInfoHolder {
* @param data The comment's payload data. * @param data The comment's payload data.
* @return Whether the holder was populated. * @return Whether the holder was populated.
*/ */
public boolean setFromComment(String name, String data) { private boolean setFromComment(String name, String data) {
if (!GAPLESS_COMMENT_ID.equals(name)) { if (!GAPLESS_COMMENT_ID.equals(name)) {
return false; return false;
} }
......
...@@ -27,6 +27,8 @@ import com.google.android.exoplayer2.extractor.MpegAudioHeader; ...@@ -27,6 +27,8 @@ 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.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.EOFException; import java.io.EOFException;
...@@ -57,6 +59,10 @@ public final class Mp3Extractor implements Extractor { ...@@ -57,6 +59,10 @@ public final class Mp3Extractor implements Extractor {
* The maximum number of bytes to peek when sniffing, excluding the ID3 header, before giving up. * The maximum number of bytes to peek when sniffing, excluding the ID3 header, before giving up.
*/ */
private static final int MAX_SNIFF_BYTES = MpegAudioHeader.MAX_FRAME_SIZE_BYTES; private static final int MAX_SNIFF_BYTES = MpegAudioHeader.MAX_FRAME_SIZE_BYTES;
/**
* Maximum length of data read into {@link #scratch}.
*/
private static final int SCRATCH_LENGTH = 10;
/** /**
* Mask that includes the audio header values that must match between frames. * Mask that includes the audio header values that must match between frames.
...@@ -77,6 +83,7 @@ public final class Mp3Extractor implements Extractor { ...@@ -77,6 +83,7 @@ public final class Mp3Extractor implements Extractor {
private int synchronizedHeaderData; private int synchronizedHeaderData;
private Metadata metadata;
private Seeker seeker; private Seeker seeker;
private long basisTimeUs; private long basisTimeUs;
private long samplesRead; private long samplesRead;
...@@ -97,7 +104,7 @@ public final class Mp3Extractor implements Extractor { ...@@ -97,7 +104,7 @@ public final class Mp3Extractor implements Extractor {
*/ */
public Mp3Extractor(long forcedFirstSampleTimestampUs) { public Mp3Extractor(long forcedFirstSampleTimestampUs) {
this.forcedFirstSampleTimestampUs = forcedFirstSampleTimestampUs; this.forcedFirstSampleTimestampUs = forcedFirstSampleTimestampUs;
scratch = new ParsableByteArray(4); scratch = new ParsableByteArray(SCRATCH_LENGTH);
synchronizedHeader = new MpegAudioHeader(); synchronizedHeader = new MpegAudioHeader();
gaplessInfoHolder = new GaplessInfoHolder(); gaplessInfoHolder = new GaplessInfoHolder();
basisTimeUs = C.TIME_UNSET; basisTimeUs = C.TIME_UNSET;
...@@ -144,7 +151,7 @@ public final class Mp3Extractor implements Extractor { ...@@ -144,7 +151,7 @@ public final class Mp3Extractor implements Extractor {
trackOutput.format(Format.createAudioSampleFormat(null, synchronizedHeader.mimeType, null, trackOutput.format(Format.createAudioSampleFormat(null, synchronizedHeader.mimeType, null,
Format.NO_VALUE, MpegAudioHeader.MAX_FRAME_SIZE_BYTES, synchronizedHeader.channels, Format.NO_VALUE, MpegAudioHeader.MAX_FRAME_SIZE_BYTES, synchronizedHeader.channels,
synchronizedHeader.sampleRate, Format.NO_VALUE, gaplessInfoHolder.encoderDelay, synchronizedHeader.sampleRate, Format.NO_VALUE, gaplessInfoHolder.encoderDelay,
gaplessInfoHolder.encoderPadding, null, null, 0, null)); gaplessInfoHolder.encoderPadding, null, null, 0, null, metadata));
} }
return readSample(input); return readSample(input);
} }
...@@ -199,7 +206,7 @@ public final class Mp3Extractor implements Extractor { ...@@ -199,7 +206,7 @@ public final class Mp3Extractor implements Extractor {
int searchLimitBytes = sniffing ? MAX_SNIFF_BYTES : MAX_SYNC_BYTES; int searchLimitBytes = sniffing ? MAX_SNIFF_BYTES : MAX_SYNC_BYTES;
input.resetPeekPosition(); input.resetPeekPosition();
if (input.getPosition() == 0) { if (input.getPosition() == 0) {
Id3Util.parseId3(input, gaplessInfoHolder); peekId3Data(input);
peekedId3Bytes = (int) input.getPeekPosition(); peekedId3Bytes = (int) input.getPeekPosition();
if (!sniffing) { if (!sniffing) {
input.skipFully(peekedId3Bytes); input.skipFully(peekedId3Bytes);
...@@ -254,6 +261,45 @@ public final class Mp3Extractor implements Extractor { ...@@ -254,6 +261,45 @@ public final class Mp3Extractor implements Extractor {
} }
/** /**
* Peeks ID3 data from the input, including gapless playback information.
*
* @param input The {@link ExtractorInput} from which data should be peeked.
* @throws IOException If an error occurred peeking from the input.
* @throws InterruptedException If the thread was interrupted.
*/
private void peekId3Data(ExtractorInput input) throws IOException, InterruptedException {
int peekedId3Bytes = 0;
while (true) {
input.peekFully(scratch.data, 0, Id3Decoder.ID3_HEADER_LENGTH);
scratch.setPosition(0);
if (scratch.readUnsignedInt24() != Id3Decoder.ID3_TAG) {
// Not an ID3 tag.
break;
}
scratch.skipBytes(3); // Skip major version, minor version and flags.
int framesLength = scratch.readSynchSafeInt();
int tagLength = Id3Decoder.ID3_HEADER_LENGTH + framesLength;
if (metadata == null) {
byte[] id3Data = new byte[tagLength];
System.arraycopy(scratch.data, 0, id3Data, 0, Id3Decoder.ID3_HEADER_LENGTH);
input.peekFully(id3Data, Id3Decoder.ID3_HEADER_LENGTH, framesLength);
metadata = new Id3Decoder().decode(id3Data, tagLength);
if (metadata != null) {
gaplessInfoHolder.setFromMetadata(metadata);
}
} else {
input.advancePeekPosition(framesLength);
}
peekedId3Bytes += tagLength;
}
input.resetPeekPosition();
input.advancePeekPosition(peekedId3Bytes);
}
/**
* Returns a {@link Seeker} to seek using metadata read from {@code input}, which should provide * Returns a {@link Seeker} to seek using metadata read from {@code input}, which should provide
* data from the start of the first frame in the stream. On returning, the input's position will * data from the start of the first frame in the stream. On returning, the input's position will
* be set to the start of the first frame of audio. * be set to the start of the first frame of audio.
......
...@@ -132,7 +132,6 @@ import java.util.List; ...@@ -132,7 +132,6 @@ import java.util.List;
public static final int TYPE_vp08 = Util.getIntegerCodeForString("vp08"); public static final int TYPE_vp08 = Util.getIntegerCodeForString("vp08");
public static final int TYPE_vp09 = Util.getIntegerCodeForString("vp09"); public static final int TYPE_vp09 = Util.getIntegerCodeForString("vp09");
public static final int TYPE_vpcC = Util.getIntegerCodeForString("vpcC"); public static final int TYPE_vpcC = Util.getIntegerCodeForString("vpcC");
public static final int TYPE_DASHES = Util.getIntegerCodeForString("----");
public final int type; public final int type;
...@@ -299,7 +298,7 @@ import java.util.List; ...@@ -299,7 +298,7 @@ import java.util.List;
* @return The corresponding four character string. * @return The corresponding four character string.
*/ */
public static String getAtomTypeString(int type) { public static String getAtomTypeString(int type) {
return "" + (char) (type >> 24) return "" + (char) ((type >> 24) & 0xFF)
+ (char) ((type >> 16) & 0xFF) + (char) ((type >> 16) & 0xFF)
+ (char) ((type >> 8) & 0xFF) + (char) ((type >> 8) & 0xFF)
+ (char) (type & 0xFF); + (char) (type & 0xFF);
......
...@@ -23,6 +23,7 @@ import com.google.android.exoplayer2.ParserException; ...@@ -23,6 +23,7 @@ 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.GaplessInfoHolder; import com.google.android.exoplayer2.extractor.GaplessInfoHolder;
import com.google.android.exoplayer2.metadata.Metadata;
import com.google.android.exoplayer2.util.Assertions; import com.google.android.exoplayer2.util.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;
...@@ -30,6 +31,7 @@ import com.google.android.exoplayer2.util.ParsableByteArray; ...@@ -30,6 +31,7 @@ import com.google.android.exoplayer2.util.ParsableByteArray;
import com.google.android.exoplayer2.util.Util; import com.google.android.exoplayer2.util.Util;
import com.google.android.exoplayer2.video.AvcConfig; import com.google.android.exoplayer2.video.AvcConfig;
import com.google.android.exoplayer2.video.HevcConfig; import com.google.android.exoplayer2.video.HevcConfig;
import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
...@@ -400,80 +402,54 @@ import java.util.List; ...@@ -400,80 +402,54 @@ import java.util.List;
* *
* @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 Parsed metadata, or null.
*/ */
public static void parseUdta(Atom.LeafAtom udtaAtom, boolean isQuickTime, GaplessInfoHolder out) { public static Metadata parseUdta(Atom.LeafAtom udtaAtom, boolean isQuickTime) {
if (isQuickTime) { 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);
while (udtaData.bytesLeft() >= Atom.HEADER_SIZE) { while (udtaData.bytesLeft() >= Atom.HEADER_SIZE) {
int atomPosition = udtaData.getPosition();
int atomSize = udtaData.readInt(); int atomSize = udtaData.readInt();
int atomType = udtaData.readInt(); int atomType = udtaData.readInt();
if (atomType == Atom.TYPE_meta) { if (atomType == Atom.TYPE_meta) {
udtaData.setPosition(udtaData.getPosition() - Atom.HEADER_SIZE); udtaData.setPosition(atomPosition);
udtaData.setLimit(udtaData.getPosition() + atomSize); return parseMetaAtom(udtaData, atomPosition + atomSize);
parseMetaAtom(udtaData, out);
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 meta, int limit) {
data.skipBytes(Atom.FULL_HEADER_SIZE); meta.skipBytes(Atom.FULL_HEADER_SIZE);
ParsableByteArray ilst = new ParsableByteArray(); while (meta.getPosition() < limit) {
while (data.bytesLeft() >= Atom.HEADER_SIZE) { int atomPosition = meta.getPosition();
int payloadSize = data.readInt() - Atom.HEADER_SIZE; int atomSize = meta.readInt();
int atomType = data.readInt(); int atomType = meta.readInt();
if (atomType == Atom.TYPE_ilst) { if (atomType == Atom.TYPE_ilst) {
ilst.reset(data.data, data.getPosition() + payloadSize); meta.setPosition(atomPosition);
ilst.setPosition(data.getPosition()); return parseIlst(meta, atomPosition + atomSize);
parseIlst(ilst, out);
if (out.hasGaplessInfo()) {
return;
}
}
data.skipBytes(payloadSize);
}
}
private static void parseIlst(ParsableByteArray ilst, GaplessInfoHolder out) {
while (ilst.bytesLeft() > 0) {
int position = ilst.getPosition();
int endPosition = position + ilst.readInt();
int type = ilst.readInt();
if (type == Atom.TYPE_DASHES) {
String lastCommentMean = null;
String lastCommentName = null;
String lastCommentData = null;
while (ilst.getPosition() < endPosition) {
int length = ilst.readInt() - Atom.FULL_HEADER_SIZE;
int key = ilst.readInt();
ilst.skipBytes(4);
if (key == Atom.TYPE_mean) {
lastCommentMean = ilst.readString(length);
} else if (key == Atom.TYPE_name) {
lastCommentName = ilst.readString(length);
} else if (key == Atom.TYPE_data) {
ilst.skipBytes(4);
lastCommentData = ilst.readString(length - 4);
} else {
ilst.skipBytes(length);
} }
meta.skipBytes(atomSize - Atom.HEADER_SIZE);
} }
if (lastCommentName != null && lastCommentData != null return null;
&& "com.apple.iTunes".equals(lastCommentMean)) {
out.setFromComment(lastCommentName, lastCommentData);
break;
} }
} else {
ilst.setPosition(endPosition); private static Metadata parseIlst(ParsableByteArray ilst, int limit) {
ilst.skipBytes(Atom.HEADER_SIZE);
ArrayList<Metadata.Entry> entries = new ArrayList<>();
while (ilst.getPosition() < limit) {
Metadata.Entry entry = MetadataUtil.parseIlstElement(ilst);
if (entry != null) {
entries.add(entry);
} }
} }
return entries.isEmpty() ? null : new Metadata(entries);
} }
/** /**
...@@ -484,12 +460,9 @@ import java.util.List; ...@@ -484,12 +460,9 @@ import java.util.List;
*/ */
private static long parseMvhd(ParsableByteArray mvhd) { private static long parseMvhd(ParsableByteArray mvhd) {
mvhd.setPosition(Atom.HEADER_SIZE); mvhd.setPosition(Atom.HEADER_SIZE);
int fullAtom = mvhd.readInt(); int fullAtom = mvhd.readInt();
int version = Atom.parseFullAtomVersion(fullAtom); int version = Atom.parseFullAtomVersion(fullAtom);
mvhd.skipBytes(version == 0 ? 8 : 16); mvhd.skipBytes(version == 0 ? 8 : 16);
return mvhd.readUnsignedInt(); return mvhd.readUnsignedInt();
} }
......
...@@ -27,6 +27,7 @@ import com.google.android.exoplayer2.extractor.PositionHolder; ...@@ -27,6 +27,7 @@ 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;
...@@ -310,10 +311,14 @@ public final class Mp4Extractor implements Extractor, SeekMap { ...@@ -310,10 +311,14 @@ public final class Mp4Extractor implements Extractor, SeekMap {
List<Mp4Track> tracks = new ArrayList<>(); List<Mp4Track> tracks = new ArrayList<>();
long earliestSampleOffset = Long.MAX_VALUE; long earliestSampleOffset = Long.MAX_VALUE;
Metadata metadata = null;
GaplessInfoHolder gaplessInfoHolder = new GaplessInfoHolder(); 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 = AtomParsers.parseUdta(udta, isQuickTime);
if (metadata != null) {
gaplessInfoHolder.setFromMetadata(metadata);
}
} }
for (int i = 0; i < moov.containerChildren.size(); i++) { for (int i = 0; i < moov.containerChildren.size(); i++) {
...@@ -340,10 +345,15 @@ public final class Mp4Extractor implements Extractor, SeekMap { ...@@ -340,10 +345,15 @@ 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) {
if (gaplessInfoHolder.hasGaplessInfo()) {
format = format.copyWithGaplessInfo(gaplessInfoHolder.encoderDelay, format = format.copyWithGaplessInfo(gaplessInfoHolder.encoderDelay,
gaplessInfoHolder.encoderPadding); gaplessInfoHolder.encoderPadding);
} }
if (metadata != null) {
format = format.copyWithMetadata(metadata);
}
}
mp4Track.trackOutput.format(format); mp4Track.trackOutput.format(format);
durationUs = Math.max(durationUs, track.durationUs); durationUs = Math.max(durationUs, track.durationUs);
......
/*
* Copyright (C) 2016 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.android.exoplayer2.metadata;
import android.os.Parcel;
import android.os.Parcelable;
import java.util.Arrays;
import java.util.List;
/**
* A collection of metadata entries.
*/
public final class Metadata implements Parcelable {
/**
* A metadata entry.
*/
public interface Entry extends Parcelable {}
private final Entry[] entries;
/**
* @param entries The metadata entries.
*/
public Metadata(Entry... entries) {
this.entries = entries == null ? new Entry[0] : entries;
}
/**
* @param entries The metadata entries.
*/
public Metadata(List<? extends Entry> entries) {
if (entries != null) {
this.entries = new Entry[entries.size()];
entries.toArray(this.entries);
} else {
this.entries = new Entry[0];
}
}
/* package */ Metadata(Parcel in) {
entries = new Metadata.Entry[in.readInt()];
for (int i = 0; i < entries.length; i++) {
entries[i] = in.readParcelable(Entry.class.getClassLoader());
}
}
/**
* Returns the number of metadata entries.
*/
public int length() {
return entries.length;
}
/**
* Returns the entry at the specified index.
*
* @param index The index of the entry.
* @return The entry at the specified index.
*/
public Metadata.Entry get(int index) {
return entries[index];
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null || getClass() != obj.getClass()) {
return false;
}
Metadata other = (Metadata) obj;
return Arrays.equals(entries, other.entries);
}
@Override
public int hashCode() {
return Arrays.hashCode(entries);
}
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeInt(entries.length);
for (Entry entry : entries) {
dest.writeParcelable(entry, 0);
}
}
public static final Parcelable.Creator<Metadata> CREATOR = new Parcelable.Creator<Metadata>() {
@Override
public Metadata createFromParcel(Parcel in) {
return new Metadata(in);
}
@Override
public Metadata[] newArray(int size) {
return new Metadata[0];
}
};
}
...@@ -17,10 +17,8 @@ package com.google.android.exoplayer2.metadata; ...@@ -17,10 +17,8 @@ package com.google.android.exoplayer2.metadata;
/** /**
* Decodes metadata from binary data. * Decodes metadata from binary data.
*
* @param <T> The type of the metadata.
*/ */
public interface MetadataDecoder<T> { public interface MetadataDecoder {
/** /**
* Checks whether the decoder supports a given mime type. * Checks whether the decoder supports a given mime type.
...@@ -38,6 +36,6 @@ public interface MetadataDecoder<T> { ...@@ -38,6 +36,6 @@ public interface MetadataDecoder<T> {
* @return The decoded metadata object. * @return The decoded metadata object.
* @throws MetadataDecoderException If a problem occurred decoding the data. * @throws MetadataDecoderException If a problem occurred decoding the data.
*/ */
T decode(byte[] data, int size) throws MetadataDecoderException; Metadata decode(byte[] data, int size) throws MetadataDecoderException;
} }
...@@ -30,38 +30,34 @@ import java.nio.ByteBuffer; ...@@ -30,38 +30,34 @@ import java.nio.ByteBuffer;
/** /**
* A renderer for metadata. * A renderer for metadata.
*
* @param <T> The type of the metadata.
*/ */
public final class MetadataRenderer<T> extends BaseRenderer implements Callback { public final class MetadataRenderer extends BaseRenderer implements Callback {
/** /**
* Receives output from a {@link MetadataRenderer}. * Receives output from a {@link MetadataRenderer}.
*
* @param <T> The type of the metadata.
*/ */
public interface Output<T> { public interface Output {
/** /**
* Called each time there is a metadata associated with current playback time. * Called each time there is a metadata associated with current playback time.
* *
* @param metadata The metadata. * @param metadata The metadata.
*/ */
void onMetadata(T metadata); void onMetadata(Metadata metadata);
} }
private static final int MSG_INVOKE_RENDERER = 0; private static final int MSG_INVOKE_RENDERER = 0;
private final MetadataDecoder<T> metadataDecoder; private final MetadataDecoder metadataDecoder;
private final Output<T> output; private final Output output;
private final Handler outputHandler; private final Handler outputHandler;
private final FormatHolder formatHolder; private final FormatHolder formatHolder;
private final DecoderInputBuffer buffer; private final DecoderInputBuffer buffer;
private boolean inputStreamEnded; private boolean inputStreamEnded;
private long pendingMetadataTimestamp; private long pendingMetadataTimestamp;
private T pendingMetadata; private Metadata pendingMetadata;
/** /**
* @param output The output. * @param output The output.
...@@ -72,8 +68,7 @@ public final class MetadataRenderer<T> extends BaseRenderer implements Callback ...@@ -72,8 +68,7 @@ public final class MetadataRenderer<T> extends BaseRenderer implements Callback
* called directly on the player's internal rendering thread. * called directly on the player's internal rendering thread.
* @param metadataDecoder A decoder for the metadata. * @param metadataDecoder A decoder for the metadata.
*/ */
public MetadataRenderer(Output<T> output, Looper outputLooper, public MetadataRenderer(Output output, Looper outputLooper, MetadataDecoder metadataDecoder) {
MetadataDecoder<T> metadataDecoder) {
super(C.TRACK_TYPE_METADATA); super(C.TRACK_TYPE_METADATA);
this.output = Assertions.checkNotNull(output); this.output = Assertions.checkNotNull(output);
this.outputHandler = outputLooper == null ? null : new Handler(outputLooper, this); this.outputHandler = outputLooper == null ? null : new Handler(outputLooper, this);
...@@ -137,7 +132,7 @@ public final class MetadataRenderer<T> extends BaseRenderer implements Callback ...@@ -137,7 +132,7 @@ public final class MetadataRenderer<T> extends BaseRenderer implements Callback
return true; return true;
} }
private void invokeRenderer(T metadata) { private void invokeRenderer(Metadata metadata) {
if (outputHandler != null) { if (outputHandler != null) {
outputHandler.obtainMessage(MSG_INVOKE_RENDERER, metadata).sendToTarget(); outputHandler.obtainMessage(MSG_INVOKE_RENDERER, metadata).sendToTarget();
} else { } else {
...@@ -150,13 +145,13 @@ public final class MetadataRenderer<T> extends BaseRenderer implements Callback ...@@ -150,13 +145,13 @@ public final class MetadataRenderer<T> extends BaseRenderer implements Callback
public boolean handleMessage(Message msg) { public boolean handleMessage(Message msg) {
switch (msg.what) { switch (msg.what) {
case MSG_INVOKE_RENDERER: case MSG_INVOKE_RENDERER:
invokeRendererInternal((T) msg.obj); invokeRendererInternal((Metadata) msg.obj);
return true; return true;
} }
return false; return false;
} }
private void invokeRendererInternal(T metadata) { private void invokeRendererInternal(Metadata metadata) {
output.onMetadata(metadata); output.onMetadata(metadata);
} }
......
...@@ -15,6 +15,11 @@ ...@@ -15,6 +15,11 @@
*/ */
package com.google.android.exoplayer2.metadata.id3; package com.google.android.exoplayer2.metadata.id3;
import android.os.Parcel;
import android.os.Parcelable;
import com.google.android.exoplayer2.util.Util;
import java.util.Arrays;
/** /**
* APIC (Attached Picture) ID3 frame. * APIC (Attached Picture) ID3 frame.
*/ */
...@@ -35,4 +40,58 @@ public final class ApicFrame extends Id3Frame { ...@@ -35,4 +40,58 @@ public final class ApicFrame extends Id3Frame {
this.pictureData = pictureData; this.pictureData = pictureData;
} }
/* package */ ApicFrame(Parcel in) {
super(ID);
mimeType = in.readString();
description = in.readString();
pictureType = in.readInt();
pictureData = in.createByteArray();
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null || getClass() != obj.getClass()) {
return false;
}
ApicFrame other = (ApicFrame) obj;
return pictureType == other.pictureType && Util.areEqual(mimeType, other.mimeType)
&& Util.areEqual(description, other.description)
&& Arrays.equals(pictureData, other.pictureData);
}
@Override
public int hashCode() {
int result = 17;
result = 31 * result + pictureType;
result = 31 * result + (mimeType != null ? mimeType.hashCode() : 0);
result = 31 * result + (description != null ? description.hashCode() : 0);
result = 31 * result + Arrays.hashCode(pictureData);
return result;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeString(mimeType);
dest.writeString(description);
dest.writeInt(pictureType);
dest.writeByteArray(pictureData);
}
public static final Parcelable.Creator<ApicFrame> CREATOR = new Parcelable.Creator<ApicFrame>() {
@Override
public ApicFrame createFromParcel(Parcel in) {
return new ApicFrame(in);
}
@Override
public ApicFrame[] newArray(int size) {
return new ApicFrame[size];
}
};
} }
...@@ -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.
*/ */
...@@ -22,9 +26,55 @@ public final class BinaryFrame extends Id3Frame { ...@@ -22,9 +26,55 @@ public final class BinaryFrame extends Id3Frame {
public final byte[] data; public final byte[] data;
public BinaryFrame(String type, byte[] data) { public BinaryFrame(String id, byte[] data) {
super(type); super(id);
this.data = data; this.data = data;
} }
/* package */ BinaryFrame(Parcel in) {
super(in.readString());
data = in.createByteArray();
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null || getClass() != obj.getClass()) {
return false;
}
BinaryFrame other = (BinaryFrame) obj;
return id.equals(other.id) && Arrays.equals(data, other.data);
}
@Override
public int hashCode() {
int result = 17;
result = 31 * result + id.hashCode();
result = 31 * result + Arrays.hashCode(data);
return result;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeString(id);
dest.writeByteArray(data);
}
public static final Parcelable.Creator<BinaryFrame> CREATOR =
new Parcelable.Creator<BinaryFrame>() {
@Override
public BinaryFrame createFromParcel(Parcel in) {
return new BinaryFrame(in);
}
@Override
public BinaryFrame[] newArray(int size) {
return new BinaryFrame[size];
}
};
} }
/*
* Copyright (C) 2016 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.android.exoplayer2.metadata.id3;
import android.os.Parcel;
import android.os.Parcelable;
import com.google.android.exoplayer2.util.Util;
/**
* Comment ID3 frame.
*/
public final class CommentFrame extends Id3Frame {
public static final String ID = "COMM";
public final String language;
public final String description;
public final String text;
public CommentFrame(String language, String description, String text) {
super(ID);
this.language = language;
this.description = description;
this.text = text;
}
/* package */ CommentFrame(Parcel in) {
super(ID);
language = in.readString();
description = in.readString();
text = in.readString();
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null || getClass() != obj.getClass()) {
return false;
}
CommentFrame other = (CommentFrame) obj;
return Util.areEqual(description, other.description) && Util.areEqual(language, other.language)
&& Util.areEqual(text, other.text);
}
@Override
public int hashCode() {
int result = 17;
result = 31 * result + (language != null ? language.hashCode() : 0);
result = 31 * result + (description != null ? description.hashCode() : 0);
result = 31 * result + (text != null ? text.hashCode() : 0);
return result;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeString(id);
dest.writeString(language);
dest.writeString(text);
}
public static final Parcelable.Creator<CommentFrame> CREATOR =
new Parcelable.Creator<CommentFrame>() {
@Override
public CommentFrame createFromParcel(Parcel in) {
return new CommentFrame(in);
}
@Override
public CommentFrame[] newArray(int size) {
return new CommentFrame[size];
}
};
}
...@@ -15,6 +15,11 @@ ...@@ -15,6 +15,11 @@
*/ */
package com.google.android.exoplayer2.metadata.id3; package com.google.android.exoplayer2.metadata.id3;
import android.os.Parcel;
import android.os.Parcelable;
import com.google.android.exoplayer2.util.Util;
import java.util.Arrays;
/** /**
* GEOB (General Encapsulated Object) ID3 frame. * GEOB (General Encapsulated Object) ID3 frame.
*/ */
...@@ -35,4 +40,57 @@ public final class GeobFrame extends Id3Frame { ...@@ -35,4 +40,57 @@ public final class GeobFrame extends Id3Frame {
this.data = data; this.data = data;
} }
/* package */ GeobFrame(Parcel in) {
super(ID);
mimeType = in.readString();
filename = in.readString();
description = in.readString();
data = in.createByteArray();
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null || getClass() != obj.getClass()) {
return false;
}
GeobFrame other = (GeobFrame) obj;
return Util.areEqual(mimeType, other.mimeType) && Util.areEqual(filename, other.filename)
&& Util.areEqual(description, other.description) && Arrays.equals(data, other.data);
}
@Override
public int hashCode() {
int result = 17;
result = 31 * result + (mimeType != null ? mimeType.hashCode() : 0);
result = 31 * result + (filename != null ? filename.hashCode() : 0);
result = 31 * result + (description != null ? description.hashCode() : 0);
result = 31 * result + Arrays.hashCode(data);
return result;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeString(mimeType);
dest.writeString(filename);
dest.writeString(description);
dest.writeByteArray(data);
}
public static final Parcelable.Creator<GeobFrame> CREATOR = new Parcelable.Creator<GeobFrame>() {
@Override
public GeobFrame createFromParcel(Parcel in) {
return new GeobFrame(in);
}
@Override
public GeobFrame[] newArray(int size) {
return new GeobFrame[size];
}
};
} }
...@@ -15,10 +15,13 @@ ...@@ -15,10 +15,13 @@
*/ */
package com.google.android.exoplayer2.metadata.id3; package com.google.android.exoplayer2.metadata.id3;
import com.google.android.exoplayer2.metadata.Metadata;
import com.google.android.exoplayer2.util.Assertions;
/** /**
* Base class for ID3 frames. * Base class for ID3 frames.
*/ */
public abstract class Id3Frame { public abstract class Id3Frame implements Metadata.Entry {
/** /**
* The frame ID. * The frame ID.
...@@ -26,7 +29,12 @@ public abstract class Id3Frame { ...@@ -26,7 +29,12 @@ public abstract class Id3Frame {
public final String id; public final String id;
public Id3Frame(String id) { public Id3Frame(String id) {
this.id = id; this.id = Assertions.checkNotNull(id);
}
@Override
public int describeContents() {
return 0;
} }
} }
...@@ -15,6 +15,11 @@ ...@@ -15,6 +15,11 @@
*/ */
package com.google.android.exoplayer2.metadata.id3; package com.google.android.exoplayer2.metadata.id3;
import android.os.Parcel;
import android.os.Parcelable;
import com.google.android.exoplayer2.util.Util;
import java.util.Arrays;
/** /**
* PRIV (Private) ID3 frame. * PRIV (Private) ID3 frame.
*/ */
...@@ -31,4 +36,50 @@ public final class PrivFrame extends Id3Frame { ...@@ -31,4 +36,50 @@ public final class PrivFrame extends Id3Frame {
this.privateData = privateData; this.privateData = privateData;
} }
/* package */ PrivFrame(Parcel in) {
super(ID);
owner = in.readString();
privateData = in.createByteArray();
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null || getClass() != obj.getClass()) {
return false;
}
PrivFrame other = (PrivFrame) obj;
return Util.areEqual(owner, other.owner) && Arrays.equals(privateData, other.privateData);
}
@Override
public int hashCode() {
int result = 17;
result = 31 * result + (owner != null ? owner.hashCode() : 0);
result = 31 * result + Arrays.hashCode(privateData);
return result;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeString(owner);
dest.writeByteArray(privateData);
}
public static final Parcelable.Creator<PrivFrame> CREATOR = new Parcelable.Creator<PrivFrame>() {
@Override
public PrivFrame createFromParcel(Parcel in) {
return new PrivFrame(in);
}
@Override
public PrivFrame[] newArray(int size) {
return new PrivFrame[size];
}
};
} }
...@@ -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 com.google.android.exoplayer2.util.Util;
/** /**
* Text information ("T000" - "TZZZ", excluding "TXXX") ID3 frame. * Text information ("T000" - "TZZZ", excluding "TXXX") ID3 frame.
*/ */
...@@ -27,4 +31,50 @@ public final class TextInformationFrame extends Id3Frame { ...@@ -27,4 +31,50 @@ public final class TextInformationFrame extends Id3Frame {
this.description = description; this.description = description;
} }
/* package */ TextInformationFrame(Parcel in) {
super(in.readString());
description = in.readString();
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null || getClass() != obj.getClass()) {
return false;
}
TextInformationFrame other = (TextInformationFrame) obj;
return id.equals(other.id) && Util.areEqual(description, other.description);
}
@Override
public int hashCode() {
int result = 17;
result = 31 * result + id.hashCode();
result = 31 * result + (description != null ? description.hashCode() : 0);
return result;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeString(id);
dest.writeString(description);
}
public static final Parcelable.Creator<TextInformationFrame> CREATOR =
new Parcelable.Creator<TextInformationFrame>() {
@Override
public TextInformationFrame createFromParcel(Parcel in) {
return new TextInformationFrame(in);
}
@Override
public TextInformationFrame[] newArray(int size) {
return new TextInformationFrame[size];
}
};
} }
...@@ -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 com.google.android.exoplayer2.util.Util;
/** /**
* TXXX (User defined text information) ID3 frame. * TXXX (User defined text information) ID3 frame.
*/ */
...@@ -31,4 +35,50 @@ public final class TxxxFrame extends Id3Frame { ...@@ -31,4 +35,50 @@ public final class TxxxFrame extends Id3Frame {
this.value = value; this.value = value;
} }
/* package */ TxxxFrame(Parcel in) {
super(ID);
description = in.readString();
value = in.readString();
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null || getClass() != obj.getClass()) {
return false;
}
TxxxFrame other = (TxxxFrame) obj;
return Util.areEqual(description, other.description) && Util.areEqual(value, other.value);
}
@Override
public int hashCode() {
int result = 17;
result = 31 * result + (description != null ? description.hashCode() : 0);
result = 31 * result + (value != null ? value.hashCode() : 0);
return result;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeString(description);
dest.writeString(value);
}
public static final Parcelable.Creator<TxxxFrame> CREATOR = new Parcelable.Creator<TxxxFrame>() {
@Override
public TxxxFrame createFromParcel(Parcel in) {
return new TxxxFrame(in);
}
@Override
public TxxxFrame[] newArray(int size) {
return new TxxxFrame[size];
}
};
} }
...@@ -17,10 +17,13 @@ package com.google.android.exoplayer2.ui; ...@@ -17,10 +17,13 @@ package com.google.android.exoplayer2.ui;
import android.content.Context; import android.content.Context;
import android.content.res.TypedArray; import android.content.res.TypedArray;
import android.support.annotation.IntDef;
import android.util.AttributeSet; import android.util.AttributeSet;
import android.widget.FrameLayout; import android.widget.FrameLayout;
import com.google.android.exoplayer2.R; import com.google.android.exoplayer2.R;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
/** /**
* A {@link FrameLayout} that resizes itself to match a specified aspect ratio. * A {@link FrameLayout} that resizes itself to match a specified aspect ratio.
...@@ -28,6 +31,13 @@ import com.google.android.exoplayer2.R; ...@@ -28,6 +31,13 @@ import com.google.android.exoplayer2.R;
public final class AspectRatioFrameLayout extends FrameLayout { public final class AspectRatioFrameLayout extends FrameLayout {
/** /**
* Resize modes for {@link AspectRatioFrameLayout}.
*/
@Retention(RetentionPolicy.SOURCE)
@IntDef({RESIZE_MODE_FIT, RESIZE_MODE_FIXED_WIDTH, RESIZE_MODE_FIXED_HEIGHT})
public @interface ResizeMode {}
/**
* Either the width or height is decreased to obtain the desired aspect ratio. * Either the width or height is decreased to obtain the desired aspect ratio.
*/ */
public static final int RESIZE_MODE_FIT = 0; public static final int RESIZE_MODE_FIT = 0;
...@@ -85,12 +95,11 @@ public final class AspectRatioFrameLayout extends FrameLayout { ...@@ -85,12 +95,11 @@ public final class AspectRatioFrameLayout extends FrameLayout {
} }
/** /**
* Sets the resize mode which can be of value {@link #RESIZE_MODE_FIT}, * Sets the resize mode.
* {@link #RESIZE_MODE_FIXED_HEIGHT} or {@link #RESIZE_MODE_FIXED_WIDTH}.
* *
* @param resizeMode The resize mode. * @param resizeMode The resize mode.
*/ */
public void setResizeMode(int resizeMode) { public void setResizeMode(@ResizeMode int resizeMode) {
if (this.resizeMode != resizeMode) { if (this.resizeMode != resizeMode) {
this.resizeMode = resizeMode; this.resizeMode = resizeMode;
requestLayout(); requestLayout();
......
...@@ -120,7 +120,7 @@ public class PlaybackControlView extends FrameLayout { ...@@ -120,7 +120,7 @@ public class PlaybackControlView extends FrameLayout {
public PlaybackControlView(Context context, AttributeSet attrs, int defStyleAttr) { public PlaybackControlView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr); super(context, attrs, defStyleAttr);
int layoutResourceId = R.layout.exo_playback_control_view; int controllerLayoutId = R.layout.exo_playback_control_view;
rewindMs = DEFAULT_REWIND_MS; rewindMs = DEFAULT_REWIND_MS;
fastForwardMs = DEFAULT_FAST_FORWARD_MS; fastForwardMs = DEFAULT_FAST_FORWARD_MS;
showTimeoutMs = DEFAULT_SHOW_TIMEOUT_MS; showTimeoutMs = DEFAULT_SHOW_TIMEOUT_MS;
...@@ -132,8 +132,8 @@ public class PlaybackControlView extends FrameLayout { ...@@ -132,8 +132,8 @@ public class PlaybackControlView extends FrameLayout {
fastForwardMs = a.getInt(R.styleable.PlaybackControlView_fastforward_increment, fastForwardMs = a.getInt(R.styleable.PlaybackControlView_fastforward_increment,
fastForwardMs); fastForwardMs);
showTimeoutMs = a.getInt(R.styleable.PlaybackControlView_show_timeout, showTimeoutMs); showTimeoutMs = a.getInt(R.styleable.PlaybackControlView_show_timeout, showTimeoutMs);
layoutResourceId = a.getResourceId(R.styleable.PlaybackControlView_controller_layout_id, controllerLayoutId = a.getResourceId(R.styleable.PlaybackControlView_controller_layout_id,
layoutResourceId); controllerLayoutId);
} finally { } finally {
a.recycle(); a.recycle();
} }
...@@ -143,7 +143,7 @@ public class PlaybackControlView extends FrameLayout { ...@@ -143,7 +143,7 @@ public class PlaybackControlView extends FrameLayout {
formatter = new Formatter(formatBuilder, Locale.getDefault()); formatter = new Formatter(formatBuilder, Locale.getDefault());
componentListener = new ComponentListener(); componentListener = new ComponentListener();
LayoutInflater.from(context).inflate(layoutResourceId, this); LayoutInflater.from(context).inflate(controllerLayoutId, this);
time = (TextView) findViewById(R.id.exo_time); time = (TextView) findViewById(R.id.exo_time);
timeCurrent = (TextView) findViewById(R.id.exo_time_current); timeCurrent = (TextView) findViewById(R.id.exo_time_current);
progressBar = (SeekBar) findViewById(R.id.exo_progress); progressBar = (SeekBar) findViewById(R.id.exo_progress);
......
...@@ -424,6 +424,24 @@ public final class ParsableByteArray { ...@@ -424,6 +424,24 @@ public final class ParsableByteArray {
} }
/** /**
* Reads the next {@code length} bytes as UTF-8 characters. A terminating NUL byte is ignored,
* if present.
*
* @param length The number of bytes to read.
* @return The string encoded by the bytes.
*/
public String readNullTerminatedString(int length) {
int stringLength = length;
int lastIndex = position + length - 1;
if (lastIndex < limit && data[lastIndex] == 0) {
stringLength--;
}
String result = new String(data, position, stringLength, Charset.defaultCharset());
position += length;
return result;
}
/**
* Reads the next {@code length} bytes as characters in the specified {@link Charset}. * 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.
......
...@@ -28,22 +28,22 @@ ...@@ -28,22 +28,22 @@
android:paddingTop="4dp" android:paddingTop="4dp"
android:orientation="horizontal"> android:orientation="horizontal">
<ImageButton android:id="@+id/exo_prev" <ImageButton android:id="@id/exo_prev"
style="@style/ExoMediaButton.Previous"/> style="@style/ExoMediaButton.Previous"/>
<ImageButton android:id="@+id/exo_rew" <ImageButton android:id="@id/exo_rew"
style="@style/ExoMediaButton.Rewind"/> style="@style/ExoMediaButton.Rewind"/>
<ImageButton android:id="@+id/exo_play" <ImageButton android:id="@id/exo_play"
style="@style/ExoMediaButton.Play"/> style="@style/ExoMediaButton.Play"/>
<ImageButton android:id="@+id/exo_pause" <ImageButton android:id="@id/exo_pause"
style="@style/ExoMediaButton.Pause"/> style="@style/ExoMediaButton.Pause"/>
<ImageButton android:id="@+id/exo_ffwd" <ImageButton android:id="@id/exo_ffwd"
style="@style/ExoMediaButton.FastForward"/> style="@style/ExoMediaButton.FastForward"/>
<ImageButton android:id="@+id/exo_next" <ImageButton android:id="@id/exo_next"
style="@style/ExoMediaButton.Next"/> style="@style/ExoMediaButton.Next"/>
</LinearLayout> </LinearLayout>
...@@ -53,7 +53,7 @@ ...@@ -53,7 +53,7 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:orientation="horizontal"> android:orientation="horizontal">
<TextView android:id="@+id/exo_time_current" <TextView android:id="@id/exo_time_current"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_gravity="center_horizontal" android:layout_gravity="center_horizontal"
...@@ -64,13 +64,13 @@ ...@@ -64,13 +64,13 @@
android:paddingEnd="4dp" android:paddingEnd="4dp"
android:textColor="#FFBEBEBE"/> android:textColor="#FFBEBEBE"/>
<SeekBar android:id="@+id/exo_progress" <SeekBar android:id="@id/exo_progress"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_weight="1" android:layout_weight="1"
android:layout_height="32dp" android:layout_height="32dp"
style="?android:attr/progressBarStyleHorizontal"/> style="?android:attr/progressBarStyleHorizontal"/>
<TextView android:id="@+id/exo_time" <TextView android:id="@id/exo_time"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_gravity="center_horizontal" android:layout_gravity="center_horizontal"
......
...@@ -17,23 +17,28 @@ ...@@ -17,23 +17,28 @@
android:layout_height="match_parent" android:layout_height="match_parent"
android:layout_width="match_parent"> android:layout_width="match_parent">
<com.google.android.exoplayer2.ui.AspectRatioFrameLayout android:id="@+id/exo_video_frame" <com.google.android.exoplayer2.ui.AspectRatioFrameLayout android:id="@id/exo_video_frame"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:layout_gravity="center"> android:layout_gravity="center">
<View android:id="@+id/exo_shutter" <View android:id="@id/exo_shutter"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:background="@android:color/black"/> android:background="@android:color/black"/>
<com.google.android.exoplayer2.ui.SubtitleView android:id="@+id/exo_subtitles" <com.google.android.exoplayer2.ui.SubtitleView android:id="@id/exo_subtitles"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent"/> android:layout_height="match_parent"/>
</com.google.android.exoplayer2.ui.AspectRatioFrameLayout> </com.google.android.exoplayer2.ui.AspectRatioFrameLayout>
<View android:id="@+id/exo_controller_placeholder" <ImageView android:id="@id/exo_artwork"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scaleType="fitCenter"/>
<View android:id="@id/exo_controller_placeholder"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent"/> android:layout_height="match_parent"/>
......
...@@ -14,24 +14,33 @@ ...@@ -14,24 +14,33 @@
limitations under the License. limitations under the License.
--> -->
<resources> <resources>
<!-- Must be kept in sync with AspectRatioFrameLayout -->
<attr name="resize_mode" format="enum"> <attr name="resize_mode" format="enum">
<enum name="fit" value="0"/> <enum name="fit" value="0"/>
<enum name="fixed_width" value="1"/> <enum name="fixed_width" value="1"/>
<enum name="fixed_height" value="2"/> <enum name="fixed_height" value="2"/>
</attr> </attr>
<!-- Must be kept in sync with SimpleExoPlayerView -->
<attr name="surface_type" format="enum">
<enum name="none" value="0"/>
<enum name="surface_view" value="1"/>
<enum name="texture_view" value="2"/>
</attr>
<attr name="show_timeout" format="integer"/> <attr name="show_timeout" format="integer"/>
<attr name="rewind_increment" format="integer"/> <attr name="rewind_increment" format="integer"/>
<attr name="fastforward_increment" format="integer"/> <attr name="fastforward_increment" format="integer"/>
<attr name="player_layout_id" format="reference"/>
<attr name="controller_layout_id" format="reference"/> <attr name="controller_layout_id" format="reference"/>
<declare-styleable name="SimpleExoPlayerView"> <declare-styleable name="SimpleExoPlayerView">
<attr name="use_artwork" format="boolean"/>
<attr name="use_controller" format="boolean"/> <attr name="use_controller" format="boolean"/>
<attr name="use_texture_view" format="boolean"/> <attr name="surface_type"/>
<attr name="show_timeout"/> <attr name="show_timeout"/>
<attr name="rewind_increment"/> <attr name="rewind_increment"/>
<attr name="fastforward_increment"/> <attr name="fastforward_increment"/>
<attr name="resize_mode"/> <attr name="resize_mode"/>
<attr name="player_layout_id"/>
<attr name="controller_layout_id"/> <attr name="controller_layout_id"/>
</declare-styleable> </declare-styleable>
......
...@@ -14,6 +14,12 @@ ...@@ -14,6 +14,12 @@
limitations under the License. limitations under the License.
--> -->
<resources> <resources>
<item name="exo_video_frame" type="id"/>
<item name="exo_shutter" type="id"/>
<item name="exo_subtitles" type="id"/>
<item name="exo_artwork" type="id"/>
<item name="exo_controller_placeholder" type="id"/>
<item name="exo_controller" type="id"/>
<item name="exo_play" type="id"/> <item name="exo_play" type="id"/>
<item name="exo_pause" type="id"/> <item name="exo_pause" type="id"/>
<item name="exo_rew" type="id"/> <item name="exo_rew" type="id"/>
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment