Commit 5a097a4c by Oliver Woodman

Merge branch 'albumMetadataV2.1' of git://github.com/cbfiddle/ExoPlayer into…

Merge branch 'albumMetadataV2.1' of git://github.com/cbfiddle/ExoPlayer into cbfiddle-albumMetadataV2.1
parents 93c2133f 776da107
Showing with 783 additions and 343 deletions
...@@ -27,8 +27,11 @@ import com.google.android.exoplayer2.Timeline; ...@@ -27,8 +27,11 @@ import com.google.android.exoplayer2.Timeline;
import com.google.android.exoplayer2.audio.AudioRendererEventListener; import com.google.android.exoplayer2.audio.AudioRendererEventListener;
import com.google.android.exoplayer2.decoder.DecoderCounters; import com.google.android.exoplayer2.decoder.DecoderCounters;
import com.google.android.exoplayer2.drm.StreamingDrmSessionManager; import com.google.android.exoplayer2.drm.StreamingDrmSessionManager;
import com.google.android.exoplayer2.extractor.GaplessInfo;
import com.google.android.exoplayer2.metadata.Metadata;
import com.google.android.exoplayer2.metadata.MetadataRenderer; import com.google.android.exoplayer2.metadata.MetadataRenderer;
import com.google.android.exoplayer2.metadata.id3.ApicFrame; import com.google.android.exoplayer2.metadata.id3.ApicFrame;
import com.google.android.exoplayer2.metadata.id3.CommentFrame;
import com.google.android.exoplayer2.metadata.id3.GeobFrame; import com.google.android.exoplayer2.metadata.id3.GeobFrame;
import com.google.android.exoplayer2.metadata.id3.Id3Frame; import com.google.android.exoplayer2.metadata.id3.Id3Frame;
import com.google.android.exoplayer2.metadata.id3.PrivFrame; import com.google.android.exoplayer2.metadata.id3.PrivFrame;
...@@ -38,6 +41,7 @@ import com.google.android.exoplayer2.source.AdaptiveMediaSourceEventListener; ...@@ -38,6 +41,7 @@ import com.google.android.exoplayer2.source.AdaptiveMediaSourceEventListener;
import com.google.android.exoplayer2.source.ExtractorMediaSource; import com.google.android.exoplayer2.source.ExtractorMediaSource;
import com.google.android.exoplayer2.source.TrackGroup; import com.google.android.exoplayer2.source.TrackGroup;
import com.google.android.exoplayer2.source.TrackGroupArray; import com.google.android.exoplayer2.source.TrackGroupArray;
import com.google.android.exoplayer2.trackselection.MappingTrackSelector;
import com.google.android.exoplayer2.trackselection.MappingTrackSelector.MappedTrackInfo; import com.google.android.exoplayer2.trackselection.MappingTrackSelector.MappedTrackInfo;
import com.google.android.exoplayer2.trackselection.TrackSelection; import com.google.android.exoplayer2.trackselection.TrackSelection;
import com.google.android.exoplayer2.trackselection.TrackSelections; import com.google.android.exoplayer2.trackselection.TrackSelections;
...@@ -55,7 +59,7 @@ import java.util.Locale; ...@@ -55,7 +59,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,
TrackSelector.EventListener<MappedTrackInfo>, MetadataRenderer.Output<List<Id3Frame>> { MappingTrackSelector.EventListener<MappedTrackInfo>, MetadataRenderer.Output<Metadata> {
private static final String TAG = "EventLogger"; private static final String TAG = "EventLogger";
private static final int MAX_TIMELINE_ITEM_LINES = 3; private static final int MAX_TIMELINE_ITEM_LINES = 3;
...@@ -175,10 +179,11 @@ import java.util.Locale; ...@@ -175,10 +179,11 @@ import java.util.Locale;
Log.d(TAG, "]"); Log.d(TAG, "]");
} }
// MetadataRenderer.Output<List<Id3Frame>> // MetadataRenderer.Output<Metadata>
@Override @Override
public void onMetadata(List<Id3Frame> id3Frames) { public void onMetadata(Metadata metadata) {
List<Id3Frame> id3Frames = metadata.getFrames();
for (Id3Frame id3Frame : id3Frames) { for (Id3Frame id3Frame : id3Frames) {
if (id3Frame instanceof TxxxFrame) { if (id3Frame instanceof TxxxFrame) {
TxxxFrame txxxFrame = (TxxxFrame) id3Frame; TxxxFrame txxxFrame = (TxxxFrame) id3Frame;
...@@ -199,10 +204,19 @@ import java.util.Locale; ...@@ -199,10 +204,19 @@ import java.util.Locale;
TextInformationFrame textInformationFrame = (TextInformationFrame) id3Frame; TextInformationFrame textInformationFrame = (TextInformationFrame) id3Frame;
Log.i(TAG, String.format("ID3 TimedMetadata %s: description=%s", textInformationFrame.id, Log.i(TAG, String.format("ID3 TimedMetadata %s: description=%s", textInformationFrame.id,
textInformationFrame.description)); textInformationFrame.description));
} else if (id3Frame instanceof CommentFrame) {
CommentFrame commentFrame = (CommentFrame) id3Frame;
Log.i(TAG, String.format("ID3 TimedMetadata %s: language=%s text=%s", commentFrame.id,
commentFrame.language, commentFrame.text));
} else { } else {
Log.i(TAG, String.format("ID3 TimedMetadata %s", id3Frame.id)); Log.i(TAG, String.format("ID3 TimedMetadata %s", id3Frame.id));
} }
} }
GaplessInfo gaplessInfo = metadata.getGaplessInfo();
if (gaplessInfo != null) {
Log.i(TAG, String.format("ID3 TimedMetadata encoder delay=%d padding=%d",
gaplessInfo.encoderDelay, gaplessInfo.encoderPadding));
}
} }
// AudioRendererEventListener // AudioRendererEventListener
......
...@@ -17,6 +17,7 @@ package com.google.android.exoplayer2.metadata.id3; ...@@ -17,6 +17,7 @@ package com.google.android.exoplayer2.metadata.id3;
import android.test.MoreAsserts; import android.test.MoreAsserts;
import com.google.android.exoplayer2.metadata.MetadataDecoderException; import com.google.android.exoplayer2.metadata.MetadataDecoderException;
import com.google.android.exoplayer2.metadata.Metadata;
import java.util.List; import java.util.List;
import junit.framework.TestCase; import junit.framework.TestCase;
...@@ -30,7 +31,8 @@ public class Id3DecoderTest extends TestCase { ...@@ -30,7 +31,8 @@ public class Id3DecoderTest extends TestCase {
3, 0, 109, 100, 105, 97, 108, 111, 103, 95, 86, 73, 78, 68, 73, 67, 79, 49, 53, 50, 55, 54, 3, 0, 109, 100, 105, 97, 108, 111, 103, 95, 86, 73, 78, 68, 73, 67, 79, 49, 53, 50, 55, 54,
54, 52, 95, 115, 116, 97, 114, 116, 0}; 54, 52, 95, 115, 116, 97, 114, 116, 0};
Id3Decoder decoder = new Id3Decoder(); Id3Decoder decoder = new Id3Decoder();
List<Id3Frame> id3Frames = decoder.decode(rawId3, rawId3.length); Metadata metadata = decoder.decode(rawId3, rawId3.length);
List<Id3Frame> id3Frames = metadata.getFrames();
assertEquals(1, id3Frames.size()); assertEquals(1, id3Frames.size());
TxxxFrame txxxFrame = (TxxxFrame) id3Frames.get(0); TxxxFrame txxxFrame = (TxxxFrame) id3Frames.get(0);
assertEquals("", txxxFrame.description); assertEquals("", txxxFrame.description);
...@@ -42,7 +44,8 @@ public class Id3DecoderTest extends TestCase { ...@@ -42,7 +44,8 @@ public class Id3DecoderTest extends TestCase {
3, 105, 109, 97, 103, 101, 47, 106, 112, 101, 103, 0, 16, 72, 101, 108, 108, 111, 32, 87, 3, 105, 109, 97, 103, 101, 47, 106, 112, 101, 103, 0, 16, 72, 101, 108, 108, 111, 32, 87,
111, 114, 108, 100, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0}; 111, 114, 108, 100, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0};
Id3Decoder decoder = new Id3Decoder(); Id3Decoder decoder = new Id3Decoder();
List<Id3Frame> id3Frames = decoder.decode(rawId3, rawId3.length); Metadata metadata = decoder.decode(rawId3, rawId3.length);
List<Id3Frame> id3Frames = metadata.getFrames();
assertEquals(1, id3Frames.size()); assertEquals(1, id3Frames.size());
ApicFrame apicFrame = (ApicFrame) id3Frames.get(0); ApicFrame apicFrame = (ApicFrame) id3Frames.get(0);
assertEquals("image/jpeg", apicFrame.mimeType); assertEquals("image/jpeg", apicFrame.mimeType);
...@@ -56,7 +59,8 @@ public class Id3DecoderTest extends TestCase { ...@@ -56,7 +59,8 @@ public class Id3DecoderTest extends TestCase {
byte[] rawId3 = new byte[] {73, 68, 51, 4, 0, 0, 0, 0, 0, 23, 84, 73, 84, 50, 0, 0, 0, 13, 0, 0, byte[] rawId3 = new byte[] {73, 68, 51, 4, 0, 0, 0, 0, 0, 23, 84, 73, 84, 50, 0, 0, 0, 13, 0, 0,
3, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 0}; 3, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 0};
Id3Decoder decoder = new Id3Decoder(); Id3Decoder decoder = new Id3Decoder();
List<Id3Frame> id3Frames = decoder.decode(rawId3, rawId3.length); Metadata metadata = decoder.decode(rawId3, rawId3.length);
List<Id3Frame> id3Frames = metadata.getFrames();
assertEquals(1, id3Frames.size()); assertEquals(1, id3Frames.size());
TextInformationFrame textInformationFrame = (TextInformationFrame) id3Frames.get(0); TextInformationFrame textInformationFrame = (TextInformationFrame) id3Frames.get(0);
assertEquals("TIT2", textInformationFrame.id); assertEquals("TIT2", textInformationFrame.id);
......
...@@ -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.text.Cue; import com.google.android.exoplayer2.text.Cue;
import com.google.android.exoplayer2.text.TextRenderer; import com.google.android.exoplayer2.text.TextRenderer;
...@@ -111,7 +111,7 @@ public final class SimpleExoPlayer implements ExoPlayer { ...@@ -111,7 +111,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<Metadata> id3Output;
private VideoListener videoListener; private VideoListener videoListener;
private AudioRendererEventListener audioDebugListener; private AudioRendererEventListener audioDebugListener;
private VideoRendererEventListener videoDebugListener; private VideoRendererEventListener videoDebugListener;
...@@ -393,7 +393,7 @@ public final class SimpleExoPlayer implements ExoPlayer { ...@@ -393,7 +393,7 @@ public final class SimpleExoPlayer implements ExoPlayer {
* *
* @param output The output. * @param output The output.
*/ */
public void setId3Output(MetadataRenderer.Output<List<Id3Frame>> output) { public void setId3Output(MetadataRenderer.Output<Metadata> output) {
id3Output = output; id3Output = output;
} }
...@@ -539,7 +539,7 @@ public final class SimpleExoPlayer implements ExoPlayer { ...@@ -539,7 +539,7 @@ public final class SimpleExoPlayer implements ExoPlayer {
Renderer textRenderer = new TextRenderer(componentListener, mainHandler.getLooper()); Renderer textRenderer = new TextRenderer(componentListener, mainHandler.getLooper());
renderersList.add(textRenderer); renderersList.add(textRenderer);
MetadataRenderer<List<Id3Frame>> id3Renderer = new MetadataRenderer<>(componentListener, MetadataRenderer<Metadata> id3Renderer = new MetadataRenderer<>(componentListener,
mainHandler.getLooper(), new Id3Decoder()); mainHandler.getLooper(), new Id3Decoder());
renderersList.add(id3Renderer); renderersList.add(id3Renderer);
} }
...@@ -636,7 +636,7 @@ public final class SimpleExoPlayer implements ExoPlayer { ...@@ -636,7 +636,7 @@ public final class SimpleExoPlayer implements ExoPlayer {
} }
private final class ComponentListener implements VideoRendererEventListener, private final class ComponentListener implements VideoRendererEventListener,
AudioRendererEventListener, TextRenderer.Output, MetadataRenderer.Output<List<Id3Frame>>, AudioRendererEventListener, TextRenderer.Output, MetadataRenderer.Output<Metadata>,
SurfaceHolder.Callback, TextureView.SurfaceTextureListener, SurfaceHolder.Callback, TextureView.SurfaceTextureListener,
TrackSelector.EventListener<Object> { TrackSelector.EventListener<Object> {
...@@ -768,12 +768,12 @@ public final class SimpleExoPlayer implements ExoPlayer { ...@@ -768,12 +768,12 @@ public final class SimpleExoPlayer implements ExoPlayer {
} }
} }
// MetadataRenderer.Output<List<Id3Frame>> implementation // MetadataRenderer.Output<Metadata> implementation
@Override @Override
public void onMetadata(List<Id3Frame> id3Frames) { public void onMetadata(Metadata metadata) {
if (id3Output != null) { if (id3Output != null) {
id3Output.onMetadata(id3Frames); id3Output.onMetadata(metadata);
} }
} }
......
/*
* Copyright (C) 2016 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.android.exoplayer2.extractor;
import android.util.Log;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* Gapless playback information.
*/
public final class GaplessInfo {
private static final String GAPLESS_COMMENT_ID = "iTunSMPB";
private static final Pattern GAPLESS_COMMENT_PATTERN = Pattern.compile("^ [0-9a-fA-F]{8} ([0-9a-fA-F]{8}) ([0-9a-fA-F]{8})");
/**
* The number of samples to trim from the start of the decoded audio stream.
*/
public final int encoderDelay;
/**
* The number of samples to trim from the end of the decoded audio stream.
*/
public final int encoderPadding;
/**
* Parses gapless playback information from a gapless playback comment (stored in an ID3 header
* or MPEG 4 user data), if valid and non-zero.
* @param name The comment's identifier.
* @param data The comment's payload data.
* @return the gapless playback info, or null if the provided data is not valid.
*/
public static GaplessInfo createFromComment(String name, String data) {
if(!GAPLESS_COMMENT_ID.equals(name)) {
return null;
} else {
Matcher matcher = GAPLESS_COMMENT_PATTERN.matcher(data);
if(matcher.find()) {
try {
int encoderDelay = Integer.parseInt(matcher.group(1), 16);
int encoderPadding = Integer.parseInt(matcher.group(2), 16);
if(encoderDelay > 0 || encoderPadding > 0) {
Log.d("ExoplayerImpl", "Parsed gapless info: " + encoderDelay + " " + encoderPadding);
return new GaplessInfo(encoderDelay, encoderPadding);
}
} catch (NumberFormatException var5) {
;
}
}
// Ignore incorrectly formatted comments.
Log.d("ExoplayerImpl", "Unable to parse gapless info: " + data);
return null;
}
}
/**
* Parses gapless playback information from an MP3 Xing header, if valid and non-zero.
*
* @param value The 24-bit value to decode.
* @return the gapless playback info, or null if the provided data is not valid.
*/
public static GaplessInfo createFromXingHeaderValue(int value) {
int encoderDelay = value >> 12;
int encoderPadding = value & 0x0FFF;
return encoderDelay > 0 || encoderPadding > 0 ?
new GaplessInfo(encoderDelay, encoderPadding) :
null;
}
public GaplessInfo(int encoderDelay, int encoderPadding) {
this.encoderDelay = encoderDelay;
this.encoderPadding = encoderPadding;
}
}
...@@ -15,90 +15,11 @@ ...@@ -15,90 +15,11 @@
*/ */
package com.google.android.exoplayer2.extractor; package com.google.android.exoplayer2.extractor;
import com.google.android.exoplayer2.Format;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/** /**
* Holder for gapless playback information. * Holder for gapless playback information.
*/ */
public final class GaplessInfoHolder { public final class GaplessInfoHolder {
private static final String GAPLESS_COMMENT_ID = "iTunSMPB"; public GaplessInfo gaplessInfo;
private static final Pattern GAPLESS_COMMENT_PATTERN =
Pattern.compile("^ [0-9a-fA-F]{8} ([0-9a-fA-F]{8}) ([0-9a-fA-F]{8})");
/**
* The number of samples to trim from the start of the decoded audio stream, or
* {@link Format#NO_VALUE} if not set.
*/
public int encoderDelay;
/**
* The number of samples to trim from the end of the decoded audio stream, or
* {@link Format#NO_VALUE} if not set.
*/
public int encoderPadding;
/**
* Creates a new holder for gapless playback information.
*/
public GaplessInfoHolder() {
encoderDelay = Format.NO_VALUE;
encoderPadding = Format.NO_VALUE;
}
/**
* Populates the holder with data from an MP3 Xing header, if valid and non-zero.
*
* @param value The 24-bit value to decode.
* @return Whether the holder was populated.
*/
public boolean setFromXingHeaderValue(int value) {
int encoderDelay = value >> 12;
int encoderPadding = value & 0x0FFF;
if (encoderDelay > 0 || encoderPadding > 0) {
this.encoderDelay = encoderDelay;
this.encoderPadding = encoderPadding;
return true;
}
return false;
}
/**
* Populates the holder with data parsed from a gapless playback comment (stored in an ID3 header
* or MPEG 4 user data), if valid and non-zero.
*
* @param name The comment's identifier.
* @param data The comment's payload data.
* @return Whether the holder was populated.
*/
public boolean setFromComment(String name, String data) {
if (!GAPLESS_COMMENT_ID.equals(name)) {
return false;
}
Matcher matcher = GAPLESS_COMMENT_PATTERN.matcher(data);
if (matcher.find()) {
try {
int encoderDelay = Integer.parseInt(matcher.group(1), 16);
int encoderPadding = Integer.parseInt(matcher.group(2), 16);
if (encoderDelay > 0 || encoderPadding > 0) {
this.encoderDelay = encoderDelay;
this.encoderPadding = encoderPadding;
return true;
}
} catch (NumberFormatException e) {
// Ignore incorrectly formatted comments.
}
}
return false;
}
/**
* Returns whether {@link #encoderDelay} and {@link #encoderPadding} have been set.
*/
public boolean hasGaplessInfo() {
return encoderDelay != Format.NO_VALUE && encoderPadding != Format.NO_VALUE;
}
} }
...@@ -22,11 +22,12 @@ import com.google.android.exoplayer2.extractor.Extractor; ...@@ -22,11 +22,12 @@ import com.google.android.exoplayer2.extractor.Extractor;
import com.google.android.exoplayer2.extractor.ExtractorInput; import com.google.android.exoplayer2.extractor.ExtractorInput;
import com.google.android.exoplayer2.extractor.ExtractorOutput; import com.google.android.exoplayer2.extractor.ExtractorOutput;
import com.google.android.exoplayer2.extractor.ExtractorsFactory; import com.google.android.exoplayer2.extractor.ExtractorsFactory;
import com.google.android.exoplayer2.extractor.GaplessInfoHolder; import com.google.android.exoplayer2.extractor.GaplessInfo;
import com.google.android.exoplayer2.extractor.MpegAudioHeader; import com.google.android.exoplayer2.extractor.MpegAudioHeader;
import com.google.android.exoplayer2.extractor.PositionHolder; import com.google.android.exoplayer2.extractor.PositionHolder;
import com.google.android.exoplayer2.extractor.SeekMap; import com.google.android.exoplayer2.extractor.SeekMap;
import com.google.android.exoplayer2.extractor.TrackOutput; import com.google.android.exoplayer2.extractor.TrackOutput;
import com.google.android.exoplayer2.metadata.Metadata;
import com.google.android.exoplayer2.util.ParsableByteArray; import com.google.android.exoplayer2.util.ParsableByteArray;
import com.google.android.exoplayer2.util.Util; import com.google.android.exoplayer2.util.Util;
import java.io.EOFException; import java.io.EOFException;
...@@ -69,7 +70,7 @@ public final class Mp3Extractor implements Extractor { ...@@ -69,7 +70,7 @@ public final class Mp3Extractor implements Extractor {
private final long forcedFirstSampleTimestampUs; private final long forcedFirstSampleTimestampUs;
private final ParsableByteArray scratch; private final ParsableByteArray scratch;
private final MpegAudioHeader synchronizedHeader; private final MpegAudioHeader synchronizedHeader;
private final GaplessInfoHolder gaplessInfoHolder; private Metadata metadata;
// Extractor outputs. // Extractor outputs.
private ExtractorOutput extractorOutput; private ExtractorOutput extractorOutput;
...@@ -99,7 +100,6 @@ public final class Mp3Extractor implements Extractor { ...@@ -99,7 +100,6 @@ public final class Mp3Extractor implements Extractor {
this.forcedFirstSampleTimestampUs = forcedFirstSampleTimestampUs; this.forcedFirstSampleTimestampUs = forcedFirstSampleTimestampUs;
scratch = new ParsableByteArray(4); scratch = new ParsableByteArray(4);
synchronizedHeader = new MpegAudioHeader(); synchronizedHeader = new MpegAudioHeader();
gaplessInfoHolder = new GaplessInfoHolder();
basisTimeUs = C.TIME_UNSET; basisTimeUs = C.TIME_UNSET;
} }
...@@ -141,10 +141,21 @@ public final class Mp3Extractor implements Extractor { ...@@ -141,10 +141,21 @@ public final class Mp3Extractor implements Extractor {
if (seeker == null) { if (seeker == null) {
seeker = setupSeeker(input); seeker = setupSeeker(input);
extractorOutput.seekMap(seeker); extractorOutput.seekMap(seeker);
trackOutput.format(Format.createAudioSampleFormat(null, synchronizedHeader.mimeType, null,
Format.NO_VALUE, MpegAudioHeader.MAX_FRAME_SIZE_BYTES, synchronizedHeader.channels, GaplessInfo gaplessInfo = metadata != null ? metadata.getGaplessInfo() : null;
synchronizedHeader.sampleRate, Format.NO_VALUE, gaplessInfoHolder.encoderDelay,
gaplessInfoHolder.encoderPadding, null, null, 0, null)); Format format = Format.createAudioSampleFormat(null, synchronizedHeader.mimeType, null,
Format.NO_VALUE, MpegAudioHeader.MAX_FRAME_SIZE_BYTES, synchronizedHeader.channels,
synchronizedHeader.sampleRate, Format.NO_VALUE,
gaplessInfo != null ? gaplessInfo.encoderDelay : Format.NO_VALUE,
gaplessInfo != null ? gaplessInfo.encoderPadding : Format.NO_VALUE,
null, null, 0, null);
if (metadata != null) {
format = format.copyWithMetadata(metadata);
}
trackOutput.format(format);
} }
return readSample(input); return readSample(input);
} }
...@@ -199,7 +210,7 @@ public final class Mp3Extractor implements Extractor { ...@@ -199,7 +210,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); metadata = Id3Util.parseId3(input);
peekedId3Bytes = (int) input.getPeekPosition(); peekedId3Bytes = (int) input.getPeekPosition();
if (!sniffing) { if (!sniffing) {
input.skipFully(peekedId3Bytes); input.skipFully(peekedId3Bytes);
...@@ -285,13 +296,16 @@ public final class Mp3Extractor implements Extractor { ...@@ -285,13 +296,16 @@ public final class Mp3Extractor implements Extractor {
} }
if (headerData == XING_HEADER || headerData == INFO_HEADER) { if (headerData == XING_HEADER || headerData == INFO_HEADER) {
seeker = XingSeeker.create(synchronizedHeader, frame, position, length); seeker = XingSeeker.create(synchronizedHeader, frame, position, length);
if (seeker != null && !gaplessInfoHolder.hasGaplessInfo()) { if (seeker != null && metadata == null || metadata.getGaplessInfo() == null) {
// If there is a Xing header, read gapless playback metadata at a fixed offset. // If there is a Xing header, read gapless playback metadata at a fixed offset.
input.resetPeekPosition(); input.resetPeekPosition();
input.advancePeekPosition(xingBase + 141); input.advancePeekPosition(xingBase + 141);
input.peekFully(scratch.data, 0, 3); input.peekFully(scratch.data, 0, 3);
scratch.setPosition(0); scratch.setPosition(0);
gaplessInfoHolder.setFromXingHeaderValue(scratch.readUnsignedInt24()); GaplessInfo gaplessInfo = GaplessInfo.createFromXingHeaderValue(scratch.readUnsignedInt24());
metadata = metadata != null ?
metadata.withGaplessInfo(gaplessInfo) : new Metadata(null, gaplessInfo);
} }
input.skipFully(synchronizedHeader.frameSize); input.skipFully(synchronizedHeader.frameSize);
} else if (frame.limit() >= 40) { } else if (frame.limit() >= 40) {
......
...@@ -22,11 +22,13 @@ import com.google.android.exoplayer2.extractor.Extractor; ...@@ -22,11 +22,13 @@ import com.google.android.exoplayer2.extractor.Extractor;
import com.google.android.exoplayer2.extractor.ExtractorInput; import com.google.android.exoplayer2.extractor.ExtractorInput;
import com.google.android.exoplayer2.extractor.ExtractorOutput; import com.google.android.exoplayer2.extractor.ExtractorOutput;
import com.google.android.exoplayer2.extractor.ExtractorsFactory; import com.google.android.exoplayer2.extractor.ExtractorsFactory;
import com.google.android.exoplayer2.extractor.GaplessInfo;
import com.google.android.exoplayer2.extractor.GaplessInfoHolder; import com.google.android.exoplayer2.extractor.GaplessInfoHolder;
import com.google.android.exoplayer2.extractor.PositionHolder; import com.google.android.exoplayer2.extractor.PositionHolder;
import com.google.android.exoplayer2.extractor.SeekMap; import com.google.android.exoplayer2.extractor.SeekMap;
import com.google.android.exoplayer2.extractor.TrackOutput; import com.google.android.exoplayer2.extractor.TrackOutput;
import com.google.android.exoplayer2.extractor.mp4.Atom.ContainerAtom; import com.google.android.exoplayer2.extractor.mp4.Atom.ContainerAtom;
import com.google.android.exoplayer2.metadata.Metadata;
import com.google.android.exoplayer2.util.Assertions; import com.google.android.exoplayer2.util.Assertions;
import com.google.android.exoplayer2.util.NalUnitUtil; import com.google.android.exoplayer2.util.NalUnitUtil;
import com.google.android.exoplayer2.util.ParsableByteArray; import com.google.android.exoplayer2.util.ParsableByteArray;
...@@ -309,11 +311,16 @@ public final class Mp4Extractor implements Extractor, SeekMap { ...@@ -309,11 +311,16 @@ public final class Mp4Extractor implements Extractor, SeekMap {
long durationUs = C.TIME_UNSET; long durationUs = C.TIME_UNSET;
List<Mp4Track> tracks = new ArrayList<>(); List<Mp4Track> tracks = new ArrayList<>();
long earliestSampleOffset = Long.MAX_VALUE; long earliestSampleOffset = Long.MAX_VALUE;
GaplessInfo gaplessInfo = null;
Metadata metadata = null;
GaplessInfoHolder gaplessInfoHolder = new GaplessInfoHolder();
Atom.LeafAtom udta = moov.getLeafAtomOfType(Atom.TYPE_udta); Atom.LeafAtom udta = moov.getLeafAtomOfType(Atom.TYPE_udta);
if (udta != null) { if (udta != null) {
AtomParsers.parseUdta(udta, isQuickTime, gaplessInfoHolder); Metadata info = AtomParsers.parseUdta(udta, isQuickTime);
if (info != null) {
gaplessInfo = info.getGaplessInfo();
metadata = info;
}
} }
for (int i = 0; i < moov.containerChildren.size(); i++) { for (int i = 0; i < moov.containerChildren.size(); i++) {
...@@ -330,7 +337,10 @@ public final class Mp4Extractor implements Extractor, SeekMap { ...@@ -330,7 +337,10 @@ public final class Mp4Extractor implements Extractor, SeekMap {
Atom.ContainerAtom stblAtom = atom.getContainerAtomOfType(Atom.TYPE_mdia) Atom.ContainerAtom stblAtom = atom.getContainerAtomOfType(Atom.TYPE_mdia)
.getContainerAtomOfType(Atom.TYPE_minf).getContainerAtomOfType(Atom.TYPE_stbl); .getContainerAtomOfType(Atom.TYPE_minf).getContainerAtomOfType(Atom.TYPE_stbl);
GaplessInfoHolder gaplessInfoHolder = new GaplessInfoHolder();
gaplessInfoHolder.gaplessInfo = gaplessInfo;
TrackSampleTable trackSampleTable = AtomParsers.parseStbl(track, stblAtom, gaplessInfoHolder); TrackSampleTable trackSampleTable = AtomParsers.parseStbl(track, stblAtom, gaplessInfoHolder);
gaplessInfo = gaplessInfoHolder.gaplessInfo;
if (trackSampleTable.sampleCount == 0) { if (trackSampleTable.sampleCount == 0) {
continue; continue;
} }
...@@ -340,9 +350,11 @@ public final class Mp4Extractor implements Extractor, SeekMap { ...@@ -340,9 +350,11 @@ public final class Mp4Extractor implements Extractor, SeekMap {
// Allow ten source samples per output sample, like the platform extractor. // Allow ten source samples per output sample, like the platform extractor.
int maxInputSize = trackSampleTable.maximumSize + 3 * 10; int maxInputSize = trackSampleTable.maximumSize + 3 * 10;
Format format = track.format.copyWithMaxInputSize(maxInputSize); Format format = track.format.copyWithMaxInputSize(maxInputSize);
if (track.type == C.TRACK_TYPE_AUDIO && gaplessInfoHolder.hasGaplessInfo()) { if (track.type == C.TRACK_TYPE_AUDIO && gaplessInfo != null) {
format = format.copyWithGaplessInfo(gaplessInfoHolder.encoderDelay, format = format.copyWithGaplessInfo(gaplessInfo.encoderDelay, gaplessInfo.encoderPadding);
gaplessInfoHolder.encoderPadding); }
if (metadata != null) {
format = format.copyWithMetadata(metadata);
} }
mp4Track.trackOutput.format(format); mp4Track.trackOutput.format(format);
......
/*
* Copyright (C) 2016 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.android.exoplayer2.metadata;
import android.os.Parcel;
import android.os.Parcelable;
import com.google.android.exoplayer2.extractor.GaplessInfo;
import com.google.android.exoplayer2.metadata.id3.Id3Frame;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
/**
* ID3 style metadata, with convenient access to gapless playback information.
*/
public class Metadata implements Parcelable {
private final List<Id3Frame> frames;
private final GaplessInfo gaplessInfo;
public Metadata(List<Id3Frame> frames, GaplessInfo gaplessInfo) {
List<Id3Frame> theFrames = frames != null ? new ArrayList<>(frames) : new ArrayList<Id3Frame>();
this.frames = Collections.unmodifiableList(theFrames);
this.gaplessInfo = gaplessInfo;
}
public Metadata(Parcel in) {
int encoderDelay = in.readInt();
int encoderPadding = in.readInt();
gaplessInfo = encoderDelay > 0 || encoderPadding > 0 ?
new GaplessInfo(encoderDelay, encoderPadding) : null;
frames = Arrays.asList((Id3Frame[]) in.readArray(Id3Frame.class.getClassLoader()));
}
public Metadata withGaplessInfo(GaplessInfo info) {
return new Metadata(frames, info);
}
public List<Id3Frame> getFrames() {
return frames;
}
public GaplessInfo getGaplessInfo() {
return gaplessInfo;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Metadata that = (Metadata) o;
if (!frames.equals(that.frames)) return false;
return gaplessInfo != null ? gaplessInfo.equals(that.gaplessInfo) : that.gaplessInfo == null;
}
@Override
public int hashCode() {
int result = frames.hashCode();
result = 31 * result + (gaplessInfo != null ? gaplessInfo.hashCode() : 0);
return result;
}
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeInt(gaplessInfo != null ? gaplessInfo.encoderDelay : -1);
dest.writeInt(gaplessInfo != null ? gaplessInfo.encoderPadding : -1);
dest.writeArray(frames.toArray(new Id3Frame[frames.size()]));
}
public static final Parcelable.Creator<Metadata> CREATOR =
new Parcelable.Creator<Metadata>() {
@Override
public Metadata createFromParcel(Parcel in) {
return new Metadata(in);
}
@Override
public Metadata[] newArray(int size) {
return new Metadata[0];
}
};
}
/*
* Copyright (C) 2016 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.android.exoplayer2.metadata;
import com.google.android.exoplayer2.extractor.GaplessInfo;
import com.google.android.exoplayer2.metadata.id3.Id3Frame;
import java.util.ArrayList;
import java.util.List;
/**
* Builder for ID3 style metadata.
*/
public class MetadataBuilder {
private List<Id3Frame> frames = new ArrayList<>();
private GaplessInfo gaplessInfo;
public void add(Id3Frame frame) {
frames.add(frame);
}
public void setGaplessInfo(GaplessInfo info) {
this.gaplessInfo = info;
}
public Metadata build() {
return !frames.isEmpty() || gaplessInfo != null ? new Metadata(frames, gaplessInfo): null;
}
}
...@@ -15,6 +15,10 @@ ...@@ -15,6 +15,10 @@
*/ */
package com.google.android.exoplayer2.metadata.id3; package com.google.android.exoplayer2.metadata.id3;
import android.os.Parcel;
import android.os.Parcelable;
import java.util.Arrays;
/** /**
* APIC (Attached Picture) ID3 frame. * APIC (Attached Picture) ID3 frame.
*/ */
...@@ -35,4 +39,62 @@ public final class ApicFrame extends Id3Frame { ...@@ -35,4 +39,62 @@ public final class ApicFrame extends Id3Frame {
this.pictureData = pictureData; this.pictureData = pictureData;
} }
public ApicFrame(Parcel in) {
super(in);
mimeType = in.readString();
description = in.readString();
pictureType = in.readInt();
pictureData = in.createByteArray();
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
ApicFrame that = (ApicFrame) o;
if (id != null ? !id.equals(that.id) : that.id != null) return false;
if (pictureType != that.pictureType) return false;
if (mimeType != null ? !mimeType.equals(that.mimeType) : that.mimeType != null)
return false;
if (description != null ? !description.equals(that.description) : that.description != null)
return false;
return Arrays.equals(pictureData, that.pictureData);
}
@Override
public int hashCode() {
int result = id != null ? id.hashCode() : 0;
result = 31 * result + (mimeType != null ? mimeType.hashCode() : 0);
result = 31 * result + (description != null ? description.hashCode() : 0);
result = 31 * result + pictureType;
result = 31 * result + Arrays.hashCode(pictureData);
return result;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeString(id);
dest.writeString(mimeType);
dest.writeString(description);
dest.writeInt(pictureType);
dest.writeByteArray(pictureData);
}
public static final Parcelable.Creator<ApicFrame> CREATOR =
new Parcelable.Creator<ApicFrame>() {
@Override
public ApicFrame createFromParcel(Parcel in) {
return new ApicFrame(in);
}
@Override
public ApicFrame[] newArray(int size) {
return new ApicFrame[size];
}
};
} }
...@@ -15,6 +15,10 @@ ...@@ -15,6 +15,10 @@
*/ */
package com.google.android.exoplayer2.metadata.id3; package com.google.android.exoplayer2.metadata.id3;
import android.os.Parcel;
import android.os.Parcelable;
import java.util.Arrays;
/** /**
* Binary ID3 frame. * Binary ID3 frame.
*/ */
...@@ -27,4 +31,49 @@ public final class BinaryFrame extends Id3Frame { ...@@ -27,4 +31,49 @@ public final class BinaryFrame extends Id3Frame {
this.data = data; this.data = data;
} }
public BinaryFrame(Parcel in) {
super(in);
data = in.createByteArray();
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
BinaryFrame that = (BinaryFrame) o;
if (id != null ? !id.equals(that.id) : that.id != null)
return false;
return Arrays.equals(data, that.data);
}
@Override
public int hashCode() {
int result = id != null ? id.hashCode() : 0;
result = 31 * result + Arrays.hashCode(data);
return result;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeString(id);
dest.writeByteArray(data);
}
public static final Parcelable.Creator<BinaryFrame> CREATOR =
new Parcelable.Creator<BinaryFrame>() {
@Override
public BinaryFrame createFromParcel(Parcel in) {
return new BinaryFrame(in);
}
@Override
public BinaryFrame[] newArray(int size) {
return new BinaryFrame[size];
}
};
} }
/*
* Copyright (C) 2016 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.android.exoplayer2.metadata.id3;
import android.os.Parcel;
import android.os.Parcelable;
/**
* Comment ID3 frame.
*/
public final class CommentFrame extends Id3Frame {
public final String language;
public final String text;
public CommentFrame(String language, String description, String text) {
super(description);
this.language = language;
this.text = text;
}
public CommentFrame(Parcel in) {
super(in);
language = in.readString();
text = in.readString();
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
CommentFrame that = (CommentFrame) o;
if (id != null ? !id.equals(that.id) : that.id != null) return false;
if (language != null ? !language.equals(that.language) : that.language != null) return false;
return text != null ? text.equals(that.text) : that.text == null;
}
@Override
public int hashCode() {
int result = id != null ? id.hashCode() : 0;
result = 31 * result + (language != null ? language.hashCode() : 0);
result = 31 * result + (text != null ? text.hashCode() : 0);
return result;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeString(id);
dest.writeString(language);
dest.writeString(text);
}
public static final Parcelable.Creator<CommentFrame> CREATOR =
new Parcelable.Creator<CommentFrame>() {
@Override
public CommentFrame createFromParcel(Parcel in) {
return new CommentFrame(in);
}
@Override
public CommentFrame[] newArray(int size) {
return new CommentFrame[size];
}
};
}
...@@ -15,6 +15,10 @@ ...@@ -15,6 +15,10 @@
*/ */
package com.google.android.exoplayer2.metadata.id3; package com.google.android.exoplayer2.metadata.id3;
import android.os.Parcel;
import android.os.Parcelable;
import java.util.Arrays;
/** /**
* GEOB (General Encapsulated Object) ID3 frame. * GEOB (General Encapsulated Object) ID3 frame.
*/ */
...@@ -35,4 +39,63 @@ public final class GeobFrame extends Id3Frame { ...@@ -35,4 +39,63 @@ public final class GeobFrame extends Id3Frame {
this.data = data; this.data = data;
} }
public GeobFrame(Parcel in) {
super(in);
mimeType = in.readString();
filename = in.readString();
description = in.readString();
data = in.createByteArray();
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
GeobFrame that = (GeobFrame) o;
if (id != null ? !id.equals(that.id) : that.id != null) return false;
if (mimeType != null ? !mimeType.equals(that.mimeType) : that.mimeType != null)
return false;
if (filename != null ? !filename.equals(that.filename) : that.filename != null)
return false;
if (description != null ? !description.equals(that.description) : that.description != null)
return false;
return Arrays.equals(data, that.data);
}
@Override
public int hashCode() {
int result = id != null ? id.hashCode() : 0;
result = 31 * result + (mimeType != null ? mimeType.hashCode() : 0);
result = 31 * result + (filename != null ? filename.hashCode() : 0);
result = 31 * result + (description != null ? description.hashCode() : 0);
result = 31 * result + Arrays.hashCode(data);
return result;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeString(id);
dest.writeString(mimeType);
dest.writeString(filename);
dest.writeString(description);
dest.writeByteArray(data);
}
public static final Parcelable.Creator<GeobFrame> CREATOR =
new Parcelable.Creator<GeobFrame>() {
@Override
public GeobFrame createFromParcel(Parcel in) {
return new GeobFrame(in);
}
@Override
public GeobFrame[] newArray(int size) {
return new GeobFrame[size];
}
};
} }
...@@ -15,10 +15,13 @@ ...@@ -15,10 +15,13 @@
*/ */
package com.google.android.exoplayer2.metadata.id3; package com.google.android.exoplayer2.metadata.id3;
import android.os.Parcel;
import android.os.Parcelable;
/** /**
* Base class for ID3 frames. * Base class for ID3 frames.
*/ */
public abstract class Id3Frame { public abstract class Id3Frame implements Parcelable {
/** /**
* The frame ID. * The frame ID.
...@@ -29,4 +32,13 @@ public abstract class Id3Frame { ...@@ -29,4 +32,13 @@ public abstract class Id3Frame {
this.id = id; this.id = id;
} }
protected Id3Frame(Parcel in) {
id = in.readString();
}
@Override
public int describeContents() {
return 0;
}
} }
...@@ -15,6 +15,10 @@ ...@@ -15,6 +15,10 @@
*/ */
package com.google.android.exoplayer2.metadata.id3; package com.google.android.exoplayer2.metadata.id3;
import android.os.Parcel;
import android.os.Parcelable;
import java.util.Arrays;
/** /**
* PRIV (Private) ID3 frame. * PRIV (Private) ID3 frame.
*/ */
...@@ -31,4 +35,52 @@ public final class PrivFrame extends Id3Frame { ...@@ -31,4 +35,52 @@ public final class PrivFrame extends Id3Frame {
this.privateData = privateData; this.privateData = privateData;
} }
public PrivFrame(Parcel in) {
super(in);
owner = in.readString();
privateData = in.createByteArray();
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
PrivFrame that = (PrivFrame) o;
if (id != null ? !id.equals(that.id) : that.id != null) return false;
if (owner != null ? !owner.equals(that.owner) : that.owner != null) return false;
return Arrays.equals(privateData, that.privateData);
}
@Override
public int hashCode() {
int result = id != null ? id.hashCode() : 0;
result = 31 * result + (owner != null ? owner.hashCode() : 0);
result = 31 * result + Arrays.hashCode(privateData);
return result;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeString(id);
dest.writeString(owner);
dest.writeByteArray(privateData);
}
public static final Parcelable.Creator<PrivFrame> CREATOR =
new Parcelable.Creator<PrivFrame>() {
@Override
public PrivFrame createFromParcel(Parcel in) {
return new PrivFrame(in);
}
@Override
public PrivFrame[] newArray(int size) {
return new PrivFrame[size];
}
};
} }
...@@ -15,6 +15,9 @@ ...@@ -15,6 +15,9 @@
*/ */
package com.google.android.exoplayer2.metadata.id3; package com.google.android.exoplayer2.metadata.id3;
import android.os.Parcel;
import android.os.Parcelable;
/** /**
* Text information ("T000" - "TZZZ", excluding "TXXX") ID3 frame. * Text information ("T000" - "TZZZ", excluding "TXXX") ID3 frame.
*/ */
...@@ -27,4 +30,48 @@ public final class TextInformationFrame extends Id3Frame { ...@@ -27,4 +30,48 @@ public final class TextInformationFrame extends Id3Frame {
this.description = description; this.description = description;
} }
public TextInformationFrame(Parcel in) {
super(in);
description = in.readString();
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
TextInformationFrame that = (TextInformationFrame) o;
if (id != null ? !id.equals(that.id) : that.id != null) return false;
return description != null ? description.equals(that.description) : that.description == null;
}
@Override
public int hashCode() {
int result = id != null ? id.hashCode() : 0;
result = 31 * result + (description != null ? description.hashCode() : 0);
return result;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeString(id);
dest.writeString(description);
}
public static final Parcelable.Creator<TextInformationFrame> CREATOR =
new Parcelable.Creator<TextInformationFrame>() {
@Override
public TextInformationFrame createFromParcel(Parcel in) {
return new TextInformationFrame(in);
}
@Override
public TextInformationFrame[] newArray(int size) {
return new TextInformationFrame[size];
}
};
} }
...@@ -15,6 +15,9 @@ ...@@ -15,6 +15,9 @@
*/ */
package com.google.android.exoplayer2.metadata.id3; package com.google.android.exoplayer2.metadata.id3;
import android.os.Parcel;
import android.os.Parcelable;
/** /**
* TXXX (User defined text information) ID3 frame. * TXXX (User defined text information) ID3 frame.
*/ */
...@@ -31,4 +34,53 @@ public final class TxxxFrame extends Id3Frame { ...@@ -31,4 +34,53 @@ public final class TxxxFrame extends Id3Frame {
this.value = value; this.value = value;
} }
public TxxxFrame(Parcel in) {
super(in);
description = in.readString();
value = in.readString();
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
TxxxFrame that = (TxxxFrame) o;
if (id != null ? !id.equals(that.id) : that.id != null) return false;
if (description != null ? !description.equals(that.description) : that.description != null)
return false;
return value != null ? value.equals(that.value) : that.value == null;
}
@Override
public int hashCode() {
int result = id != null ? id.hashCode() : 0;
result = 31 * result + (description != null ? description.hashCode() : 0);
result = 31 * result + (value != null ? value.hashCode() : 0);
return result;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeString(id);
dest.writeString(description);
dest.writeString(value);
}
public static final Parcelable.Creator<TxxxFrame> CREATOR =
new Parcelable.Creator<TxxxFrame>() {
@Override
public TxxxFrame createFromParcel(Parcel in) {
return new TxxxFrame(in);
}
@Override
public TxxxFrame[] newArray(int size) {
return new TxxxFrame[size];
}
};
} }
...@@ -142,22 +142,13 @@ public final class ContentDataSource implements DataSource { ...@@ -142,22 +142,13 @@ public final class ContentDataSource implements DataSource {
@Override @Override
public void close() throws ContentDataSourceException { public void close() throws ContentDataSourceException {
uri = null; uri = null;
try { if (inputStream != null) {
if (inputStream != null) {
inputStream.close();
}
} catch (IOException e) {
throw new ContentDataSourceException(e);
} finally {
inputStream = null;
try { try {
if (assetFileDescriptor != null) { inputStream.close();
assetFileDescriptor.close();
}
} catch (IOException e) { } catch (IOException e) {
throw new ContentDataSourceException(e); throw new ContentDataSourceException(e);
} finally { } finally {
assetFileDescriptor = null; inputStream = null;
if (opened) { if (opened) {
opened = false; opened = false;
if (listener != null) { if (listener != null) {
...@@ -166,6 +157,13 @@ public final class ContentDataSource implements DataSource { ...@@ -166,6 +157,13 @@ public final class ContentDataSource implements DataSource {
} }
} }
} }
}
if (assetFileDescriptor != null) {
try {
assetFileDescriptor.close();
} catch (Exception e) {
}
assetFileDescriptor = null;
}
}
} }
...@@ -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.
......
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