diff --git a/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImplInternal.java b/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImplInternal.java index fc946804f4..a7d569081e 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImplInternal.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImplInternal.java @@ -1543,6 +1543,7 @@ import java.util.Collections; } } + @SuppressWarnings("ParameterNotNullable") private void updatePlayingPeriodRenderers(@Nullable MediaPeriodHolder oldPlayingPeriodHolder) throws ExoPlaybackException { MediaPeriodHolder newPlayingPeriodHolder = queue.getPlayingPeriod(); diff --git a/library/core/src/main/java/com/google/android/exoplayer2/decoder/SimpleDecoder.java b/library/core/src/main/java/com/google/android/exoplayer2/decoder/SimpleDecoder.java index 68089d7b41..441d3899a3 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/decoder/SimpleDecoder.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/decoder/SimpleDecoder.java @@ -17,7 +17,7 @@ package com.google.android.exoplayer2.decoder; import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.util.Assertions; -import java.util.LinkedList; +import java.util.ArrayDeque; /** * Base class for {@link Decoder}s that use their own decode thread. @@ -28,8 +28,8 @@ public abstract class SimpleDecoder queuedInputBuffers; - private final LinkedList queuedOutputBuffers; + private final ArrayDeque queuedInputBuffers; + private final ArrayDeque queuedOutputBuffers; private final I[] availableInputBuffers; private final O[] availableOutputBuffers; @@ -48,8 +48,8 @@ public abstract class SimpleDecoder(); - queuedOutputBuffers = new LinkedList<>(); + queuedInputBuffers = new ArrayDeque<>(); + queuedOutputBuffers = new ArrayDeque<>(); availableInputBuffers = inputBuffers; availableInputBufferCount = inputBuffers.length; for (int i = 0; i < availableInputBufferCount; i++) { diff --git a/library/core/src/main/java/com/google/android/exoplayer2/drm/HttpMediaDrmCallback.java b/library/core/src/main/java/com/google/android/exoplayer2/drm/HttpMediaDrmCallback.java index 4a93ac8333..9150a72b53 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/drm/HttpMediaDrmCallback.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/drm/HttpMediaDrmCallback.java @@ -108,7 +108,8 @@ public final class HttpMediaDrmCallback implements MediaDrmCallback { @Override public byte[] executeProvisionRequest(UUID uuid, ProvisionRequest request) throws IOException { - String url = request.getDefaultUrl() + "&signedRequest=" + new String(request.getData()); + String url = + request.getDefaultUrl() + "&signedRequest=" + Util.fromUtf8Bytes(request.getData()); return executePost(dataSourceFactory, url, new byte[0], null); } diff --git a/library/core/src/main/java/com/google/android/exoplayer2/extractor/mkv/DefaultEbmlReader.java b/library/core/src/main/java/com/google/android/exoplayer2/extractor/mkv/DefaultEbmlReader.java index 21cb3775e5..c0494e1ee0 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/extractor/mkv/DefaultEbmlReader.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/extractor/mkv/DefaultEbmlReader.java @@ -24,7 +24,7 @@ import java.io.EOFException; import java.io.IOException; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; -import java.util.Stack; +import java.util.ArrayDeque; /** * Default implementation of {@link EbmlReader}. @@ -46,15 +46,21 @@ import java.util.Stack; private static final int VALID_FLOAT32_ELEMENT_SIZE_BYTES = 4; private static final int VALID_FLOAT64_ELEMENT_SIZE_BYTES = 8; - private final byte[] scratch = new byte[8]; - private final Stack masterElementsStack = new Stack<>(); - private final VarintReader varintReader = new VarintReader(); + private final byte[] scratch; + private final ArrayDeque masterElementsStack; + private final VarintReader varintReader; private EbmlReaderOutput output; private @ElementState int elementState; private int elementId; private long elementContentSize; + public DefaultEbmlReader() { + scratch = new byte[8]; + masterElementsStack = new ArrayDeque<>(); + varintReader = new VarintReader(); + } + @Override public void init(EbmlReaderOutput eventHandler) { this.output = eventHandler; @@ -100,7 +106,7 @@ import java.util.Stack; case EbmlReaderOutput.TYPE_MASTER: long elementContentPosition = input.getPosition(); long elementEndPosition = elementContentPosition + elementContentSize; - masterElementsStack.add(new MasterElement(elementId, elementEndPosition)); + masterElementsStack.push(new MasterElement(elementId, elementEndPosition)); output.startMasterElement(elementId, elementContentPosition, elementContentSize); elementState = ELEMENT_STATE_READ_ID; return true; diff --git a/library/core/src/main/java/com/google/android/exoplayer2/extractor/mkv/Sniffer.java b/library/core/src/main/java/com/google/android/exoplayer2/extractor/mkv/Sniffer.java index a3fde6d455..62c9404916 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/extractor/mkv/Sniffer.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/extractor/mkv/Sniffer.java @@ -78,8 +78,9 @@ import java.io.IOException; return false; } if (size != 0) { - input.advancePeekPosition((int) size); - peekLength += size; + int sizeInt = (int) size; + input.advancePeekPosition(sizeInt); + peekLength += sizeInt; } } return peekLength == headerStart + headerSize; diff --git a/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp4/FixedSampleSizeRechunker.java b/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp4/FixedSampleSizeRechunker.java index 8336a280a2..536f70048c 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp4/FixedSampleSizeRechunker.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp4/FixedSampleSizeRechunker.java @@ -108,4 +108,7 @@ import com.google.android.exoplayer2.util.Util; return new Results(offsets, sizes, maximumSize, timestamps, flags, duration); } + private FixedSampleSizeRechunker() { + // Prevent instantiation. + } } diff --git a/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp4/FragmentedMp4Extractor.java b/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp4/FragmentedMp4Extractor.java index d1134dc3f6..0bf42f1839 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp4/FragmentedMp4Extractor.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp4/FragmentedMp4Extractor.java @@ -50,7 +50,6 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.List; -import java.util.Stack; import java.util.UUID; /** @@ -141,7 +140,7 @@ public final class FragmentedMp4Extractor implements Extractor { // Parser state. private final ParsableByteArray atomHeader; private final byte[] extendedTypeScratch; - private final Stack containerAtoms; + private final ArrayDeque containerAtoms; private final ArrayDeque pendingMetadataSampleInfos; private final @Nullable TrackOutput additionalEmsgTrackOutput; @@ -257,7 +256,7 @@ public final class FragmentedMp4Extractor implements Extractor { nalPrefix = new ParsableByteArray(5); nalBuffer = new ParsableByteArray(); extendedTypeScratch = new byte[16]; - containerAtoms = new Stack<>(); + containerAtoms = new ArrayDeque<>(); pendingMetadataSampleInfos = new ArrayDeque<>(); trackBundles = new SparseArray<>(); durationUs = C.TIME_UNSET; @@ -390,7 +389,7 @@ public final class FragmentedMp4Extractor implements Extractor { if (shouldParseContainerAtom(atomType)) { long endPosition = input.getPosition() + atomSize - Atom.HEADER_SIZE; - containerAtoms.add(new ContainerAtom(atomType, endPosition)); + containerAtoms.push(new ContainerAtom(atomType, endPosition)); if (atomSize == atomHeaderBytesRead) { processAtomEnded(endPosition); } else { diff --git a/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp4/Mp4Extractor.java b/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp4/Mp4Extractor.java index 75bd2c16ee..e70a49a2d7 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp4/Mp4Extractor.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp4/Mp4Extractor.java @@ -37,9 +37,9 @@ import com.google.android.exoplayer2.util.Util; import java.io.IOException; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; +import java.util.ArrayDeque; import java.util.ArrayList; import java.util.List; -import java.util.Stack; /** * Extracts data from the MP4 container format. @@ -101,7 +101,7 @@ public final class Mp4Extractor implements Extractor, SeekMap { private final ParsableByteArray nalLength; private final ParsableByteArray atomHeader; - private final Stack containerAtoms; + private final ArrayDeque containerAtoms; @State private int parserState; private int atomType; @@ -137,7 +137,7 @@ public final class Mp4Extractor implements Extractor, SeekMap { public Mp4Extractor(@Flags int flags) { this.flags = flags; atomHeader = new ParsableByteArray(Atom.LONG_HEADER_SIZE); - containerAtoms = new Stack<>(); + containerAtoms = new ArrayDeque<>(); nalStartCode = new ParsableByteArray(NalUnitUtil.NAL_START_CODE); nalLength = new ParsableByteArray(4); sampleTrackIndex = C.INDEX_UNSET; @@ -303,7 +303,7 @@ public final class Mp4Extractor implements Extractor, SeekMap { if (shouldParseContainerAtom(atomType)) { long endPosition = input.getPosition() + atomSize - atomHeaderBytesRead; - containerAtoms.add(new ContainerAtom(atomType, endPosition)); + containerAtoms.push(new ContainerAtom(atomType, endPosition)); if (atomSize == atomHeaderBytesRead) { processAtomEnded(endPosition); } else { diff --git a/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp4/PsshAtomUtil.java b/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp4/PsshAtomUtil.java index 84513ef4d3..a033f5c663 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp4/PsshAtomUtil.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp4/PsshAtomUtil.java @@ -49,6 +49,7 @@ public final class PsshAtomUtil { * @param data The scheme specific data. * @return The PSSH atom. */ + @SuppressWarnings("ParameterNotNullable") public static byte[] buildPsshAtom( UUID systemId, @Nullable UUID[] keyIds, @Nullable byte[] data) { boolean buildV1Atom = keyIds != null; diff --git a/library/core/src/main/java/com/google/android/exoplayer2/extractor/ogg/OpusReader.java b/library/core/src/main/java/com/google/android/exoplayer2/extractor/ogg/OpusReader.java index 8ed8a4a01d..ce3b9ea6ba 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/extractor/ogg/OpusReader.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/extractor/ogg/OpusReader.java @@ -130,6 +130,6 @@ import java.util.List; } else { length = 10000 << length; } - return frames * length; + return (long) frames * length; } } diff --git a/library/core/src/main/java/com/google/android/exoplayer2/extractor/ogg/VorbisUtil.java b/library/core/src/main/java/com/google/android/exoplayer2/extractor/ogg/VorbisUtil.java index 79767a00d8..0235fba272 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/extractor/ogg/VorbisUtil.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/extractor/ogg/VorbisUtil.java @@ -357,12 +357,12 @@ import java.util.Arrays; for (int i = 0; i < lengthMap.length; i++) { if (isSparse) { if (bitArray.readBit()) { - lengthMap[i] = bitArray.readBits(5) + 1; + lengthMap[i] = (long) (bitArray.readBits(5) + 1); } else { // entry unused lengthMap[i] = 0; } } else { // not sparse - lengthMap[i] = bitArray.readBits(5) + 1; + lengthMap[i] = (long) (bitArray.readBits(5) + 1); } } } else { @@ -392,7 +392,7 @@ import java.util.Arrays; lookupValuesCount = 0; } } else { - lookupValuesCount = entries * dimensions; + lookupValuesCount = (long) entries * dimensions; } // discard (no decoding required yet) bitArray.skipBits((int) (lookupValuesCount * valueBits)); @@ -407,6 +407,10 @@ import java.util.Arrays; return (long) Math.floor(Math.pow(entries, 1.d / dimension)); } + private VorbisUtil() { + // Prevent instantiation. + } + public static final class CodeBook { public final int dimensions; diff --git a/library/core/src/main/java/com/google/android/exoplayer2/extractor/wav/WavHeaderReader.java b/library/core/src/main/java/com/google/android/exoplayer2/extractor/wav/WavHeaderReader.java index d0810a0629..ca745591f5 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/extractor/wav/WavHeaderReader.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/extractor/wav/WavHeaderReader.java @@ -25,7 +25,7 @@ import com.google.android.exoplayer2.util.Util; import java.io.IOException; /** Reads a {@code WavHeader} from an input stream; supports resuming from input failures. */ -/*package*/ final class WavHeaderReader { +/* package */ final class WavHeaderReader { private static final String TAG = "WavHeaderReader"; @@ -158,6 +158,10 @@ import java.io.IOException; wavHeader.setDataBounds(input.getPosition(), chunkHeader.size); } + private WavHeaderReader() { + // Prevent instantiation. + } + /** Container for a WAV chunk header. */ private static final class ChunkHeader { diff --git a/library/core/src/main/java/com/google/android/exoplayer2/offline/ProgressiveDownloader.java b/library/core/src/main/java/com/google/android/exoplayer2/offline/ProgressiveDownloader.java index cf64d26bb5..8c80a23d67 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/offline/ProgressiveDownloader.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/offline/ProgressiveDownloader.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - package com.google.android.exoplayer2.offline; +package com.google.android.exoplayer2.offline; import android.net.Uri; import com.google.android.exoplayer2.C; diff --git a/library/core/src/main/java/com/google/android/exoplayer2/offline/SegmentDownloader.java b/library/core/src/main/java/com/google/android/exoplayer2/offline/SegmentDownloader.java index 6ce2121acd..9be694264c 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/offline/SegmentDownloader.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/offline/SegmentDownloader.java @@ -201,6 +201,8 @@ public abstract class SegmentDownloader, K> throws InterruptedException, IOException; /** Initializes the download, returning a list of {@link Segment}s that need to be downloaded. */ + // Writes to downloadedSegments and downloadedBytes are safe. See the comment on download(). + @SuppressWarnings("NonAtomicVolatileUpdate") private List initDownload() throws IOException, InterruptedException { M manifest = getManifest(dataSource, manifestUri); if (!streamKeys.isEmpty()) { diff --git a/library/core/src/main/java/com/google/android/exoplayer2/source/TrackGroup.java b/library/core/src/main/java/com/google/android/exoplayer2/source/TrackGroup.java index a9fb261768..56c9989f34 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/source/TrackGroup.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/source/TrackGroup.java @@ -72,11 +72,14 @@ public final class TrackGroup implements Parcelable { } /** - * Returns the index of the track with the given format in the group. + * Returns the index of the track with the given format in the group. The format is located by + * identity so, for example, {@code group.indexOf(group.getFormat(index)) == index} even if + * multiple tracks have formats that contain the same values. * * @param format The format. * @return The index of the track, or {@link C#INDEX_UNSET} if no such track exists. */ + @SuppressWarnings("ReferenceEquality") public int indexOf(Format format) { for (int i = 0; i < formats.length; i++) { if (format == formats[i]) { diff --git a/library/core/src/main/java/com/google/android/exoplayer2/text/cea/Cea708Decoder.java b/library/core/src/main/java/com/google/android/exoplayer2/text/cea/Cea708Decoder.java index 6bdbebc73b..731c9032d6 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/text/cea/Cea708Decoder.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/text/cea/Cea708Decoder.java @@ -38,7 +38,6 @@ import com.google.android.exoplayer2.util.ParsableBitArray; import com.google.android.exoplayer2.util.ParsableByteArray; import java.util.ArrayList; import java.util.Collections; -import java.util.LinkedList; import java.util.List; /** @@ -196,7 +195,10 @@ public final class Cea708Decoder extends CeaDecoder { @Override protected void decode(SubtitleInputBuffer inputBuffer) { - ccData.reset(inputBuffer.data.array(), inputBuffer.data.limit()); + // Subtitle input buffers are non-direct and the position is zero, so calling array() is safe. + @SuppressWarnings("ByteBufferBackingArray") + byte[] inputBufferData = inputBuffer.data.array(); + ccData.reset(inputBufferData, inputBuffer.data.limit()); while (ccData.bytesLeft() >= 3) { int ccTypeAndValid = (ccData.readUnsignedByte() & 0x07); @@ -879,7 +881,7 @@ public final class Cea708Decoder extends CeaDecoder { private int row; public CueBuilder() { - rolledUpCaptions = new LinkedList<>(); + rolledUpCaptions = new ArrayList<>(); captionStringBuilder = new SpannableStringBuilder(); reset(); } diff --git a/library/core/src/main/java/com/google/android/exoplayer2/text/cea/CeaDecoder.java b/library/core/src/main/java/com/google/android/exoplayer2/text/cea/CeaDecoder.java index 07a55f1a40..3efc16bdd0 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/text/cea/CeaDecoder.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/text/cea/CeaDecoder.java @@ -24,7 +24,7 @@ import com.google.android.exoplayer2.text.SubtitleDecoderException; import com.google.android.exoplayer2.text.SubtitleInputBuffer; import com.google.android.exoplayer2.text.SubtitleOutputBuffer; import com.google.android.exoplayer2.util.Assertions; -import java.util.LinkedList; +import java.util.ArrayDeque; import java.util.PriorityQueue; /** @@ -35,8 +35,8 @@ import java.util.PriorityQueue; private static final int NUM_INPUT_BUFFERS = 10; private static final int NUM_OUTPUT_BUFFERS = 2; - private final LinkedList availableInputBuffers; - private final LinkedList availableOutputBuffers; + private final ArrayDeque availableInputBuffers; + private final ArrayDeque availableOutputBuffers; private final PriorityQueue queuedInputBuffers; private CeaInputBuffer dequeuedInputBuffer; @@ -44,11 +44,11 @@ import java.util.PriorityQueue; private long queuedInputBufferCount; public CeaDecoder() { - availableInputBuffers = new LinkedList<>(); + availableInputBuffers = new ArrayDeque<>(); for (int i = 0; i < NUM_INPUT_BUFFERS; i++) { availableInputBuffers.add(new CeaInputBuffer()); } - availableOutputBuffers = new LinkedList<>(); + availableOutputBuffers = new ArrayDeque<>(); for (int i = 0; i < NUM_OUTPUT_BUFFERS; i++) { availableOutputBuffers.add(new CeaOutputBuffer()); } diff --git a/library/core/src/main/java/com/google/android/exoplayer2/text/ssa/SsaDecoder.java b/library/core/src/main/java/com/google/android/exoplayer2/text/ssa/SsaDecoder.java index 0cb6f66898..e528a57762 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/text/ssa/SsaDecoder.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/text/ssa/SsaDecoder.java @@ -62,7 +62,7 @@ public final class SsaDecoder extends SimpleSubtitleDecoder { super("SsaDecoder"); if (initializationData != null && !initializationData.isEmpty()) { haveInitializationData = true; - String formatLine = new String(initializationData.get(0)); + String formatLine = Util.fromUtf8Bytes(initializationData.get(0)); Assertions.checkArgument(formatLine.startsWith(FORMAT_LINE_PREFIX)); parseFormatLine(formatLine); parseHeader(new ParsableByteArray(initializationData.get(1))); diff --git a/library/core/src/main/java/com/google/android/exoplayer2/text/ttml/TtmlDecoder.java b/library/core/src/main/java/com/google/android/exoplayer2/text/ttml/TtmlDecoder.java index ad8f849c60..61e0085065 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/text/ttml/TtmlDecoder.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/text/ttml/TtmlDecoder.java @@ -26,8 +26,8 @@ import com.google.android.exoplayer2.util.Util; import com.google.android.exoplayer2.util.XmlPullParserUtil; import java.io.ByteArrayInputStream; import java.io.IOException; +import java.util.ArrayDeque; import java.util.HashMap; -import java.util.LinkedList; import java.util.Map; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -109,13 +109,13 @@ public final class TtmlDecoder extends SimpleSubtitleDecoder { ByteArrayInputStream inputStream = new ByteArrayInputStream(bytes, 0, length); xmlParser.setInput(inputStream, null); TtmlSubtitle ttmlSubtitle = null; - LinkedList nodeStack = new LinkedList<>(); + ArrayDeque nodeStack = new ArrayDeque<>(); int unsupportedNodeDepth = 0; int eventType = xmlParser.getEventType(); FrameAndTickRate frameAndTickRate = DEFAULT_FRAME_AND_TICK_RATE; CellResolution cellResolution = DEFAULT_CELL_RESOLUTION; while (eventType != XmlPullParser.END_DOCUMENT) { - TtmlNode parent = nodeStack.peekLast(); + TtmlNode parent = nodeStack.peek(); if (unsupportedNodeDepth == 0) { String name = xmlParser.getName(); if (eventType == XmlPullParser.START_TAG) { @@ -131,7 +131,7 @@ public final class TtmlDecoder extends SimpleSubtitleDecoder { } else { try { TtmlNode node = parseNode(xmlParser, parent, regionMap, frameAndTickRate); - nodeStack.addLast(node); + nodeStack.push(node); if (parent != null) { parent.addChild(node); } @@ -145,9 +145,9 @@ public final class TtmlDecoder extends SimpleSubtitleDecoder { parent.addChild(TtmlNode.buildTextNode(xmlParser.getText())); } else if (eventType == XmlPullParser.END_TAG) { if (xmlParser.getName().equals(TtmlNode.TAG_TT)) { - ttmlSubtitle = new TtmlSubtitle(nodeStack.getLast(), globalStyles, regionMap); + ttmlSubtitle = new TtmlSubtitle(nodeStack.peek(), globalStyles, regionMap); } - nodeStack.removeLast(); + nodeStack.pop(); } } else { if (eventType == XmlPullParser.START_TAG) { @@ -178,7 +178,7 @@ public final class TtmlDecoder extends SimpleSubtitleDecoder { float frameRateMultiplier = 1; String frameRateMultiplierString = xmlParser.getAttributeValue(TTP, "frameRateMultiplier"); if (frameRateMultiplierString != null) { - String[] parts = frameRateMultiplierString.split(" "); + String[] parts = Util.split(frameRateMultiplierString, " "); if (parts.length != 2) { throw new SubtitleDecoderException("frameRateMultiplier doesn't have 2 parts"); } @@ -354,7 +354,8 @@ public final class TtmlDecoder extends SimpleSubtitleDecoder { } private String[] parseStyleIds(String parentStyleIds) { - return parentStyleIds.split("\\s+"); + parentStyleIds = parentStyleIds.trim(); + return parentStyleIds.isEmpty() ? new String[0] : Util.split(parentStyleIds, "\\s+"); } private TtmlStyle parseStyleAttributes(XmlPullParser parser, TtmlStyle style) { @@ -531,7 +532,7 @@ public final class TtmlDecoder extends SimpleSubtitleDecoder { private static void parseFontSize(String expression, TtmlStyle out) throws SubtitleDecoderException { - String[] expressions = expression.split("\\s+"); + String[] expressions = Util.split(expression, "\\s+"); Matcher matcher; if (expressions.length == 1) { matcher = FONT_SIZE.matcher(expression); diff --git a/library/core/src/main/java/com/google/android/exoplayer2/text/tx3g/Tx3gDecoder.java b/library/core/src/main/java/com/google/android/exoplayer2/text/tx3g/Tx3gDecoder.java index 2270ccc632..ebc38bcd70 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/text/tx3g/Tx3gDecoder.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/text/tx3g/Tx3gDecoder.java @@ -92,7 +92,8 @@ public final class Tx3gDecoder extends SimpleSubtitleDecoder { | ((initializationBytes[27] & 0xFF) << 16) | ((initializationBytes[28] & 0xFF) << 8) | (initializationBytes[29] & 0xFF); - String fontFamily = new String(initializationBytes, 43, initializationBytes.length - 43); + String fontFamily = + Util.fromUtf8Bytes(initializationBytes, 43, initializationBytes.length - 43); defaultFontFamily = TX3G_SERIF.equals(fontFamily) ? C.SERIF_NAME : C.SANS_SERIF_NAME; //font size (initializationBytes[25]) is 5% of video height calculatedVideoTrackHeight = 20 * initializationBytes[25]; diff --git a/library/core/src/main/java/com/google/android/exoplayer2/text/webvtt/CssParser.java b/library/core/src/main/java/com/google/android/exoplayer2/text/webvtt/CssParser.java index ea1e6891f0..81c362bda5 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/text/webvtt/CssParser.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/text/webvtt/CssParser.java @@ -18,6 +18,7 @@ package com.google.android.exoplayer2.text.webvtt; import android.text.TextUtils; import com.google.android.exoplayer2.util.ColorParser; import com.google.android.exoplayer2.util.ParsableByteArray; +import com.google.android.exoplayer2.util.Util; import java.util.Arrays; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -314,7 +315,7 @@ import java.util.regex.Pattern; } selector = selector.substring(0, voiceStartIndex); } - String[] classDivision = selector.split("\\."); + String[] classDivision = Util.split(selector, "\\."); String tagAndIdDivision = classDivision[0]; int idPrefixIndex = tagAndIdDivision.indexOf('#'); if (idPrefixIndex != -1) { diff --git a/library/core/src/main/java/com/google/android/exoplayer2/text/webvtt/Mp4WebvttDecoder.java b/library/core/src/main/java/com/google/android/exoplayer2/text/webvtt/Mp4WebvttDecoder.java index 159dd4f2e0..17c2366f07 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/text/webvtt/Mp4WebvttDecoder.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/text/webvtt/Mp4WebvttDecoder.java @@ -78,7 +78,8 @@ public final class Mp4WebvttDecoder extends SimpleSubtitleDecoder { int boxType = sampleData.readInt(); remainingCueBoxBytes -= BOX_HEADER_SIZE; int payloadLength = boxSize - BOX_HEADER_SIZE; - String boxPayload = new String(sampleData.data, sampleData.getPosition(), payloadLength); + String boxPayload = + Util.fromUtf8Bytes(sampleData.data, sampleData.getPosition(), payloadLength); sampleData.skipBytes(payloadLength); remainingCueBoxBytes -= payloadLength; if (boxType == TYPE_sttg) { diff --git a/library/core/src/main/java/com/google/android/exoplayer2/text/webvtt/WebvttCueParser.java b/library/core/src/main/java/com/google/android/exoplayer2/text/webvtt/WebvttCueParser.java index 80ebecdc0e..6f2a1328c6 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/text/webvtt/WebvttCueParser.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/text/webvtt/WebvttCueParser.java @@ -34,11 +34,12 @@ import android.text.style.UnderlineSpan; import android.util.Log; import com.google.android.exoplayer2.text.Cue; import com.google.android.exoplayer2.util.ParsableByteArray; +import com.google.android.exoplayer2.util.Util; +import java.util.ArrayDeque; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.List; -import java.util.Stack; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -157,7 +158,7 @@ public final class WebvttCueParser { /* package */ static void parseCueText(String id, String markup, WebvttCue.Builder builder, List styles) { SpannableStringBuilder spannedText = new SpannableStringBuilder(); - Stack startTagStack = new Stack<>(); + ArrayDeque startTagStack = new ArrayDeque<>(); List scratchStyleMatches = new ArrayList<>(); int pos = 0; while (pos < markup.length()) { @@ -456,7 +457,7 @@ public final class WebvttCueParser { if (tagExpression.isEmpty()) { return null; } - return tagExpression.split("[ \\.]")[0]; + return Util.splitAtFirst(tagExpression, "[ \\.]")[0]; } private static void getApplicableStyles(List declaredStyles, String id, @@ -518,7 +519,7 @@ public final class WebvttCueParser { voice = fullTagExpression.substring(voiceStartIndex).trim(); fullTagExpression = fullTagExpression.substring(0, voiceStartIndex); } - String[] nameAndClasses = fullTagExpression.split("\\."); + String[] nameAndClasses = Util.split(fullTagExpression, "\\."); String name = nameAndClasses[0]; String[] classes; if (nameAndClasses.length > 1) { diff --git a/library/core/src/main/java/com/google/android/exoplayer2/text/webvtt/WebvttParserUtil.java b/library/core/src/main/java/com/google/android/exoplayer2/text/webvtt/WebvttParserUtil.java index d0c3eda494..b94be19d8f 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/text/webvtt/WebvttParserUtil.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/text/webvtt/WebvttParserUtil.java @@ -17,6 +17,7 @@ package com.google.android.exoplayer2.text.webvtt; import com.google.android.exoplayer2.text.SubtitleDecoderException; import com.google.android.exoplayer2.util.ParsableByteArray; +import com.google.android.exoplayer2.util.Util; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -53,8 +54,8 @@ public final class WebvttParserUtil { */ public static long parseTimestampUs(String timestamp) throws NumberFormatException { long value = 0; - String[] parts = timestamp.split("\\.", 2); - String[] subparts = parts[0].split(":"); + String[] parts = Util.splitAtFirst(timestamp, "\\."); + String[] subparts = Util.split(parts[0], ":"); for (String subpart : subparts) { value = (value * 60) + Long.parseLong(subpart); } diff --git a/library/core/src/main/java/com/google/android/exoplayer2/trackselection/BaseTrackSelection.java b/library/core/src/main/java/com/google/android/exoplayer2/trackselection/BaseTrackSelection.java index 81eb5dd888..3f201bccea 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/trackselection/BaseTrackSelection.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/trackselection/BaseTrackSelection.java @@ -110,6 +110,7 @@ public abstract class BaseTrackSelection implements TrackSelection { } @Override + @SuppressWarnings("ReferenceEquality") public final int indexOf(Format format) { for (int i = 0; i < length; i++) { if (formats[i] == format) { @@ -183,7 +184,9 @@ public abstract class BaseTrackSelection implements TrackSelection { return hashCode; } + // Track groups are compared by identity not value, as distinct groups may have the same value. @Override + @SuppressWarnings("ReferenceEquality") public boolean equals(@Nullable Object obj) { if (this == obj) { return true; diff --git a/library/core/src/main/java/com/google/android/exoplayer2/trackselection/TrackSelection.java b/library/core/src/main/java/com/google/android/exoplayer2/trackselection/TrackSelection.java index 58616996ff..ee0a397a8d 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/trackselection/TrackSelection.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/trackselection/TrackSelection.java @@ -91,7 +91,9 @@ public interface TrackSelection { int getIndexInTrackGroup(int index); /** - * Returns the index in the selection of the track with the specified format. + * Returns the index in the selection of the track with the specified format. The format is + * located by identity so, for example, {@code selection.indexOf(selection.getFormat(index)) == + * index} even if multiple selected tracks have formats that contain the same values. * * @param format The format. * @return The index in the selection, or {@link C#INDEX_UNSET} if the track with the specified diff --git a/library/core/src/main/java/com/google/android/exoplayer2/upstream/DataSchemeDataSource.java b/library/core/src/main/java/com/google/android/exoplayer2/upstream/DataSchemeDataSource.java index c547625819..33d67f3f46 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/upstream/DataSchemeDataSource.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/upstream/DataSchemeDataSource.java @@ -19,6 +19,7 @@ import android.net.Uri; import android.util.Base64; import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.ParserException; +import com.google.android.exoplayer2.util.Util; import java.io.IOException; import java.net.URLDecoder; @@ -41,8 +42,8 @@ public final class DataSchemeDataSource implements DataSource { if (!SCHEME_DATA.equals(scheme)) { throw new ParserException("Unsupported scheme: " + scheme); } - String[] uriParts = uri.getSchemeSpecificPart().split(","); - if (uriParts.length > 2) { + String[] uriParts = Util.split(uri.getSchemeSpecificPart(), ","); + if (uriParts.length != 2) { throw new ParserException("Unexpected URI format: " + uri); } String dataString = uriParts[1]; diff --git a/library/core/src/main/java/com/google/android/exoplayer2/upstream/cache/ContentMetadataInternal.java b/library/core/src/main/java/com/google/android/exoplayer2/upstream/cache/ContentMetadataInternal.java index 3376dd6944..0065018260 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/upstream/cache/ContentMetadataInternal.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/upstream/cache/ContentMetadataInternal.java @@ -20,7 +20,7 @@ import android.support.annotation.Nullable; import com.google.android.exoplayer2.C; /** Helper classes to easily access and modify internal metadata values. */ -/*package*/ final class ContentMetadataInternal { +/* package */ final class ContentMetadataInternal { private static final String PREFIX = ContentMetadata.INTERNAL_METADATA_NAME_PREFIX; private static final String METADATA_NAME_REDIRECTED_URI = PREFIX + "redir"; @@ -59,4 +59,8 @@ import com.google.android.exoplayer2.C; public static void removeRedirectedUri(ContentMetadataMutations mutations) { mutations.remove(METADATA_NAME_REDIRECTED_URI); } + + private ContentMetadataInternal() { + // Prevent instantiation. + } } diff --git a/library/core/src/main/java/com/google/android/exoplayer2/upstream/crypto/AesFlushingCipher.java b/library/core/src/main/java/com/google/android/exoplayer2/upstream/crypto/AesFlushingCipher.java index e093eb3064..1721b1d8b7 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/upstream/crypto/AesFlushingCipher.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/upstream/crypto/AesFlushingCipher.java @@ -16,6 +16,7 @@ package com.google.android.exoplayer2.upstream.crypto; import com.google.android.exoplayer2.util.Assertions; +import com.google.android.exoplayer2.util.Util; import java.nio.ByteBuffer; import java.security.InvalidAlgorithmParameterException; import java.security.InvalidKeyException; @@ -49,7 +50,9 @@ public final class AesFlushingCipher { flushedBlock = new byte[blockSize]; long counter = offset / blockSize; int startPadding = (int) (offset % blockSize); - cipher.init(mode, new SecretKeySpec(secretKey, cipher.getAlgorithm().split("/")[0]), + cipher.init( + mode, + new SecretKeySpec(secretKey, Util.splitAtFirst(cipher.getAlgorithm(), "/")[0]), new IvParameterSpec(getInitializationVector(nonce, counter))); if (startPadding != 0) { updateInPlace(new byte[startPadding], 0, startPadding); diff --git a/library/core/src/main/java/com/google/android/exoplayer2/util/ColorParser.java b/library/core/src/main/java/com/google/android/exoplayer2/util/ColorParser.java index a9df80e9fe..54f52e0a14 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/util/ColorParser.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/util/ColorParser.java @@ -26,7 +26,7 @@ import java.util.regex.Pattern; * * @see WebVTT CSS Styling * @see Timed Text Markup Language 2 (TTML2) - 10.3.5 - **/ + */ public final class ColorParser { private static final String RGB = "rgb"; @@ -271,4 +271,7 @@ public final class ColorParser { COLOR_MAP.put("yellowgreen", 0xFF9ACD32); } + private ColorParser() { + // Prevent instantiation. + } } diff --git a/library/core/src/main/java/com/google/android/exoplayer2/util/MimeTypes.java b/library/core/src/main/java/com/google/android/exoplayer2/util/MimeTypes.java index d13aa877e0..9e9ff5fd77 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/util/MimeTypes.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/util/MimeTypes.java @@ -144,7 +144,7 @@ public final class MimeTypes { if (codecs == null) { return null; } - String[] codecList = codecs.split(","); + String[] codecList = Util.split(codecs, ","); for (String codec : codecList) { String mimeType = getMediaMimeType(codec); if (mimeType != null && isVideo(mimeType)) { @@ -164,7 +164,7 @@ public final class MimeTypes { if (codecs == null) { return null; } - String[] codecList = codecs.split(","); + String[] codecList = Util.split(codecs, ","); for (String codec : codecList) { String mimeType = getMediaMimeType(codec); if (mimeType != null && isAudio(mimeType)) { diff --git a/library/core/src/main/java/com/google/android/exoplayer2/util/ParsableBitArray.java b/library/core/src/main/java/com/google/android/exoplayer2/util/ParsableBitArray.java index fb5f9525e9..c60caf9ba8 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/util/ParsableBitArray.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/util/ParsableBitArray.java @@ -175,7 +175,7 @@ public final class ParsableBitArray { bitOffset -= 8; returnValue |= (data[byteOffset++] & 0xFF) << bitOffset; } - returnValue |= (data[byteOffset] & 0xFF) >> 8 - bitOffset; + returnValue |= (data[byteOffset] & 0xFF) >> (8 - bitOffset); returnValue &= 0xFFFFFFFF >>> (32 - numBits); if (bitOffset == 8) { bitOffset = 0; @@ -199,17 +199,18 @@ public final class ParsableBitArray { int to = offset + (numBits >> 3) /* numBits / 8 */; for (int i = offset; i < to; i++) { buffer[i] = (byte) (data[byteOffset++] << bitOffset); - buffer[i] |= (data[byteOffset] & 0xFF) >> (8 - bitOffset); + buffer[i] = (byte) (buffer[i] | ((data[byteOffset] & 0xFF) >> (8 - bitOffset))); } // Trailing bits. int bitsLeft = numBits & 7 /* numBits % 8 */; if (bitsLeft == 0) { return; } - buffer[to] &= 0xFF >> bitsLeft; // Set to 0 the bits that are going to be overwritten. + // Set bits that are going to be overwritten to 0. + buffer[to] = (byte) (buffer[to] & (0xFF >> bitsLeft)); if (bitOffset + bitsLeft > 8) { // We read the rest of data[byteOffset] and increase byteOffset. - buffer[to] |= (byte) ((data[byteOffset++] & 0xFF) << bitOffset); + buffer[to] = (byte) (buffer[to] | ((data[byteOffset++] & 0xFF) << bitOffset)); bitOffset -= 8; } bitOffset += bitsLeft; @@ -280,9 +281,10 @@ public final class ParsableBitArray { int firstByteReadSize = Math.min(8 - bitOffset, numBits); int firstByteRightPaddingSize = 8 - bitOffset - firstByteReadSize; int firstByteBitmask = (0xFF00 >> bitOffset) | ((1 << firstByteRightPaddingSize) - 1); - data[byteOffset] &= firstByteBitmask; + data[byteOffset] = (byte) (data[byteOffset] & firstByteBitmask); int firstByteInputBits = value >>> (numBits - firstByteReadSize); - data[byteOffset] |= firstByteInputBits << firstByteRightPaddingSize; + data[byteOffset] = + (byte) (data[byteOffset] | (firstByteInputBits << firstByteRightPaddingSize)); remainingBitsToRead -= firstByteReadSize; int currentByteIndex = byteOffset + 1; while (remainingBitsToRead > 8) { @@ -290,9 +292,11 @@ public final class ParsableBitArray { remainingBitsToRead -= 8; } int lastByteRightPaddingSize = 8 - remainingBitsToRead; - data[currentByteIndex] &= (1 << lastByteRightPaddingSize) - 1; + data[currentByteIndex] = + (byte) (data[currentByteIndex] & ((1 << lastByteRightPaddingSize) - 1)); int lastByteInput = value & ((1 << remainingBitsToRead) - 1); - data[currentByteIndex] |= lastByteInput << lastByteRightPaddingSize; + data[currentByteIndex] = + (byte) (data[currentByteIndex] | (lastByteInput << lastByteRightPaddingSize)); skipBits(numBits); assertValidOffset(); } diff --git a/library/core/src/main/java/com/google/android/exoplayer2/util/ParsableByteArray.java b/library/core/src/main/java/com/google/android/exoplayer2/util/ParsableByteArray.java index 57313ea895..5190896d9f 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/util/ParsableByteArray.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/util/ParsableByteArray.java @@ -470,7 +470,7 @@ public final class ParsableByteArray { if (lastIndex < limit && data[lastIndex] == 0) { stringLength--; } - String result = new String(data, position, stringLength); + String result = Util.fromUtf8Bytes(data, position, stringLength); position += length; return result; } @@ -489,7 +489,7 @@ public final class ParsableByteArray { while (stringLimit < limit && data[stringLimit] != 0) { stringLimit++; } - String string = new String(data, position, stringLimit - position); + String string = Util.fromUtf8Bytes(data, position, stringLimit - position); position = stringLimit; if (position < limit) { position++; @@ -520,7 +520,7 @@ public final class ParsableByteArray { // There's a byte order mark at the start of the line. Discard it. position += 3; } - String line = new String(data, position, lineLimit - position); + String line = Util.fromUtf8Bytes(data, position, lineLimit - position); position = lineLimit; if (position == limit) { return line; diff --git a/library/core/src/main/java/com/google/android/exoplayer2/util/ParsableNalUnitBitArray.java b/library/core/src/main/java/com/google/android/exoplayer2/util/ParsableNalUnitBitArray.java index 443c69909c..3a7202c674 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/util/ParsableNalUnitBitArray.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/util/ParsableNalUnitBitArray.java @@ -140,7 +140,7 @@ public final class ParsableNalUnitBitArray { returnValue |= (data[byteOffset] & 0xFF) << bitOffset; byteOffset += shouldSkipByte(byteOffset + 1) ? 2 : 1; } - returnValue |= (data[byteOffset] & 0xFF) >> 8 - bitOffset; + returnValue |= (data[byteOffset] & 0xFF) >> (8 - bitOffset); returnValue &= 0xFFFFFFFF >>> (32 - numBits); if (bitOffset == 8) { bitOffset = 0; diff --git a/library/core/src/main/java/com/google/android/exoplayer2/util/Util.java b/library/core/src/main/java/com/google/android/exoplayer2/util/Util.java index 37e3e119bf..90c5d17b6d 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/util/Util.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/util/Util.java @@ -332,6 +332,18 @@ public final class Util { return new String(bytes, Charset.forName(C.UTF8_NAME)); } + /** + * Returns a new {@link String} constructed by decoding UTF-8 encoded bytes in a subarray. + * + * @param bytes The UTF-8 encoded bytes to decode. + * @param offset The index of the first byte to decode. + * @param length The number of bytes to decode. + * @return The string. + */ + public static String fromUtf8Bytes(byte[] bytes, int offset, int length) { + return new String(bytes, offset, length, Charset.forName(C.UTF8_NAME)); + } + /** * Returns a new byte array containing the code points of a {@link String} encoded using UTF-8. * @@ -342,6 +354,33 @@ public final class Util { return value.getBytes(Charset.forName(C.UTF8_NAME)); } + /** + * Splits a string using {@code value.split(regex, -1}). Note: this is is similar to {@link + * String#split(String)} but empty matches at the end of the string will not be omitted from the + * returned array. + * + * @param value The string to split. + * @param regex A delimiting regular expression. + * @return The array of strings resulting from splitting the string. + */ + public static String[] split(String value, String regex) { + return value.split(regex, /* limit= */ -1); + } + + /** + * Splits the string at the first occurrence of the delimiter {@code regex}. If the delimiter does + * not match, returns an array with one element which is the input string. If the delimiter does + * match, returns an array with the portion of the string before the delimiter and the rest of the + * string. + * + * @param value The string. + * @param regex A delimiting regular expression. + * @return The string split by the first occurrence of the delimiter. + */ + public static String[] splitAtFirst(String value, String regex) { + return value.split(regex, /* limit= */ 2); + } + /** * Returns whether the given character is a carriage return ('\r') or a line feed ('\n'). * @@ -978,7 +1017,7 @@ public final class Util { if (TextUtils.isEmpty(codecs)) { return null; } - String[] codecArray = codecs.trim().split("(\\s*,\\s*)"); + String[] codecArray = split(codecs.trim(), "(\\s*,\\s*)"); StringBuilder builder = new StringBuilder(); for (String codec : codecArray) { if (trackType == MimeTypes.getTrackTypeOfCodec(codec)) { @@ -1454,7 +1493,7 @@ public final class Util { // If we managed to read sys.display-size, attempt to parse it. if (!TextUtils.isEmpty(sysDisplaySize)) { try { - String[] sysDisplaySizeParts = sysDisplaySize.trim().split("x"); + String[] sysDisplaySizeParts = split(sysDisplaySize.trim(), "x"); if (sysDisplaySizeParts.length == 2) { int width = Integer.parseInt(sysDisplaySizeParts[0]); int height = Integer.parseInt(sysDisplaySizeParts[1]);