diff --git a/library/src/main/java/com/google/android/exoplayer/ExoPlayerImplInternal.java b/library/src/main/java/com/google/android/exoplayer/ExoPlayerImplInternal.java index dcc3ba9e18..1d2410e9ac 100644 --- a/library/src/main/java/com/google/android/exoplayer/ExoPlayerImplInternal.java +++ b/library/src/main/java/com/google/android/exoplayer/ExoPlayerImplInternal.java @@ -299,13 +299,13 @@ import java.util.concurrent.atomic.AtomicInteger; durationUs = source.getDurationUs(); bufferedPositionUs = source.getBufferedPositionUs(); - TrackGroup[] trackGroups = source.getTrackGroups(); + TrackGroupArray trackGroups = source.getTrackGroups(); // The maximum number of tracks that one renderer can support is the total number of tracks in // all groups, plus possibly one adaptive track per group. int maxTrackCount = trackGroups.length; for (int groupIndex = 0; groupIndex < trackGroups.length; groupIndex++) { - maxTrackCount += trackGroups[groupIndex].length; + maxTrackCount += trackGroups.get(groupIndex).length; } // Construct tracks for each renderer. Format[][] externalTrackFormats = new Format[renderers.length][]; @@ -316,7 +316,7 @@ import java.util.concurrent.atomic.AtomicInteger; TrackSelection[] rendererTrackSelections = new TrackSelection[maxTrackCount]; Format[][] rendererTrackFormats = new Format[maxTrackCount][]; for (int groupIndex = 0; groupIndex < trackGroups.length; groupIndex++) { - TrackGroup trackGroup = trackGroups[groupIndex]; + TrackGroup trackGroup = trackGroups.get(groupIndex); // TODO[REFACTOR]: This should check that the renderer is capable of adaptive playback, in // addition to checking that the group is adaptive. if (trackGroup.adaptive) { diff --git a/library/src/main/java/com/google/android/exoplayer/Format.java b/library/src/main/java/com/google/android/exoplayer/Format.java index f9fa3a9910..39895f78f8 100644 --- a/library/src/main/java/com/google/android/exoplayer/Format.java +++ b/library/src/main/java/com/google/android/exoplayer/Format.java @@ -143,7 +143,7 @@ public final class Format { */ public final String language; - // Lazy-initialized hashcode and framework media format. + // Lazily initialized hashcode and framework media format. private int hashCode; private MediaFormat frameworkMediaFormat; diff --git a/library/src/main/java/com/google/android/exoplayer/FrameworkSampleSource.java b/library/src/main/java/com/google/android/exoplayer/FrameworkSampleSource.java index b4fb2117b3..6c472d346e 100644 --- a/library/src/main/java/com/google/android/exoplayer/FrameworkSampleSource.java +++ b/library/src/main/java/com/google/android/exoplayer/FrameworkSampleSource.java @@ -75,7 +75,7 @@ public final class FrameworkSampleSource implements SampleSource { private boolean prepared; private long durationUs; private MediaExtractor extractor; - private TrackGroup[] tracks; + private TrackGroupArray tracks; private int[] trackStates; private boolean[] pendingResets; @@ -133,7 +133,7 @@ public final class FrameworkSampleSource implements SampleSource { durationUs = C.UNKNOWN_TIME_US; trackStates = new int[extractor.getTrackCount()]; pendingResets = new boolean[trackStates.length]; - tracks = new TrackGroup[trackStates.length]; + TrackGroup[] tracks = new TrackGroup[trackStates.length]; for (int i = 0; i < trackStates.length; i++) { MediaFormat format = extractor.getTrackFormat(i); if (format.containsKey(MediaFormat.KEY_DURATION)) { @@ -141,6 +141,7 @@ public final class FrameworkSampleSource implements SampleSource { } tracks[i] = new TrackGroup(createFormat(i, format)); } + this.tracks = new TrackGroupArray(tracks); prepared = true; return true; } @@ -156,7 +157,7 @@ public final class FrameworkSampleSource implements SampleSource { } @Override - public TrackGroup[] getTrackGroups() { + public TrackGroupArray getTrackGroups() { return tracks; } @@ -194,7 +195,7 @@ public final class FrameworkSampleSource implements SampleSource { return TrackStream.NOTHING_READ; } if (trackStates[track] != TRACK_STATE_FORMAT_SENT) { - formatHolder.format = tracks[track].getFormat(0); + formatHolder.format = tracks.get(track).getFormat(0); formatHolder.drmInitData = Util.SDK_INT >= 18 ? getDrmInitDataV18() : null; trackStates[track] = TRACK_STATE_FORMAT_SENT; return TrackStream.FORMAT_READ; diff --git a/library/src/main/java/com/google/android/exoplayer/MultiSampleSource.java b/library/src/main/java/com/google/android/exoplayer/MultiSampleSource.java index 87ce02538e..5d4781dfb6 100644 --- a/library/src/main/java/com/google/android/exoplayer/MultiSampleSource.java +++ b/library/src/main/java/com/google/android/exoplayer/MultiSampleSource.java @@ -28,7 +28,7 @@ public class MultiSampleSource implements SampleSource { private boolean prepared; private long durationUs; - private TrackGroup[] tracks; + private TrackGroupArray trackGroups; public MultiSampleSource(SampleSource... sources) { this.sources = sources; @@ -53,14 +53,15 @@ public class MultiSampleSource implements SampleSource { durationUs = sources[i].getDurationUs(); } } - tracks = new TrackGroup[totalTrackGroupCount]; + TrackGroup[] trackGroups = new TrackGroup[totalTrackGroupCount]; int trackGroupIndex = 0; for (int i = 0; i < sources.length; i++) { int sourceTrackGroupCount = sources[i].getTrackGroups().length; for (int j = 0; j < sourceTrackGroupCount; j++) { - tracks[trackGroupIndex++] = sources[i].getTrackGroups()[j]; + trackGroups[trackGroupIndex++] = sources[i].getTrackGroups().get(j); } } + this.trackGroups = new TrackGroupArray(trackGroups); } return prepared; } @@ -71,8 +72,8 @@ public class MultiSampleSource implements SampleSource { } @Override - public TrackGroup[] getTrackGroups() { - return tracks; + public TrackGroupArray getTrackGroups() { + return trackGroups; } @Override diff --git a/library/src/main/java/com/google/android/exoplayer/SampleSource.java b/library/src/main/java/com/google/android/exoplayer/SampleSource.java index 6478ce3af9..1f3d7bd24b 100644 --- a/library/src/main/java/com/google/android/exoplayer/SampleSource.java +++ b/library/src/main/java/com/google/android/exoplayer/SampleSource.java @@ -58,7 +58,7 @@ public interface SampleSource { * * @return The {@link TrackGroup}s. */ - public TrackGroup[] getTrackGroups(); + public TrackGroupArray getTrackGroups(); /** * Indicates to the source that it should continue buffering data for its enabled tracks. diff --git a/library/src/main/java/com/google/android/exoplayer/SingleSampleSource.java b/library/src/main/java/com/google/android/exoplayer/SingleSampleSource.java index e46d0163c8..bf706cc295 100644 --- a/library/src/main/java/com/google/android/exoplayer/SingleSampleSource.java +++ b/library/src/main/java/com/google/android/exoplayer/SingleSampleSource.java @@ -52,7 +52,7 @@ public final class SingleSampleSource implements SampleSource, TrackStream, Load private final Format format; private final long durationUs; private final int minLoadableRetryCount; - private final TrackGroup[] trackGroups; + private final TrackGroupArray tracks; private int state; private byte[] sampleData; @@ -75,7 +75,7 @@ public final class SingleSampleSource implements SampleSource, TrackStream, Load this.format = format; this.durationUs = durationUs; this.minLoadableRetryCount = minLoadableRetryCount; - trackGroups = new TrackGroup[] {new TrackGroup(format)}; + tracks = new TrackGroupArray(new TrackGroup(format)); sampleData = new byte[INITIAL_SAMPLE_SIZE]; } @@ -105,8 +105,8 @@ public final class SingleSampleSource implements SampleSource, TrackStream, Load } @Override - public TrackGroup[] getTrackGroups() { - return trackGroups; + public TrackGroupArray getTrackGroups() { + return tracks; } @Override diff --git a/library/src/main/java/com/google/android/exoplayer/TrackGroup.java b/library/src/main/java/com/google/android/exoplayer/TrackGroup.java index 24b2646dcf..fb9d7f5d49 100644 --- a/library/src/main/java/com/google/android/exoplayer/TrackGroup.java +++ b/library/src/main/java/com/google/android/exoplayer/TrackGroup.java @@ -15,6 +15,10 @@ */ package com.google.android.exoplayer; +import com.google.android.exoplayer.util.Assertions; + +import java.util.Arrays; + /** * Defines a group of tracks exposed by a {@link SampleSource}. *

@@ -26,7 +30,7 @@ package com.google.android.exoplayer; public final class TrackGroup { /** - * The number of tracks in the group. + * The number of tracks in the group. Always greater than zero. */ public final int length; /** @@ -36,18 +40,22 @@ public final class TrackGroup { private final Format[] formats; + // Lazily initialized hashcode. + private int hashCode; + /** * @param format The format of the single track. */ public TrackGroup(Format format) { - this(false, format); + this(false, Assertions.checkNotNull(format)); } /** * @param adaptive Whether it's possible to adapt between multiple tracks in the group. - * @param formats The track formats. + * @param formats The track formats. Must not be null or empty. Must not contain null elements. */ public TrackGroup(boolean adaptive, Format... formats) { + Assertions.checkState(formats.length > 0); this.adaptive = adaptive; this.formats = formats; length = formats.length; @@ -63,4 +71,29 @@ public final class TrackGroup { return formats[index]; } + @Override + public int hashCode() { + if (hashCode == 0) { + int result = 17; + result = 31 * result + (adaptive ? 1231 : 1237); + result = 31 * result + Arrays.hashCode(formats); + result = 31 * result + length; + hashCode = result; + } + return hashCode; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null || getClass() != obj.getClass()) { + return false; + } + TrackGroup other = (TrackGroup) obj; + return adaptive == other.adaptive && length == other.length + && Arrays.equals(formats, other.formats); + } + } diff --git a/library/src/main/java/com/google/android/exoplayer/TrackGroupArray.java b/library/src/main/java/com/google/android/exoplayer/TrackGroupArray.java new file mode 100644 index 0000000000..51f387cd71 --- /dev/null +++ b/library/src/main/java/com/google/android/exoplayer/TrackGroupArray.java @@ -0,0 +1,76 @@ +/* + * 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.exoplayer; + +import java.util.Arrays; + +/** + * An array of {@link TrackGroup}s exposed by a {@link SampleSource}. + */ +public final class TrackGroupArray { + + /** + * The number of groups in the list. Greater than or equal to zero. + */ + public final int length; + + private final TrackGroup[] trackGroups; + + // Lazily initialized hashcode. + private int hashCode; + + /** + * @param trackGroups The groups. Must not be null or contain null elements, but may be empty. + */ + public TrackGroupArray(TrackGroup... trackGroups) { + this.trackGroups = trackGroups; + this.length = trackGroups.length; + } + + /** + * Gets the group at a given index. + * + * @param index The index of the group. + * @return The group. + */ + public TrackGroup get(int index) { + return trackGroups[index]; + } + + @Override + public int hashCode() { + if (hashCode == 0) { + int result = 17; + result = 31 * result + Arrays.hashCode(trackGroups); + result = 31 * result + length; + hashCode = result; + } + return hashCode; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null || getClass() != obj.getClass()) { + return false; + } + TrackGroupArray other = (TrackGroupArray) obj; + return length == other.length && Arrays.equals(trackGroups, other.trackGroups); + } + +} diff --git a/library/src/main/java/com/google/android/exoplayer/chunk/ChunkSampleSource.java b/library/src/main/java/com/google/android/exoplayer/chunk/ChunkSampleSource.java index ed55e4ea2f..0913d838df 100644 --- a/library/src/main/java/com/google/android/exoplayer/chunk/ChunkSampleSource.java +++ b/library/src/main/java/com/google/android/exoplayer/chunk/ChunkSampleSource.java @@ -22,6 +22,7 @@ import com.google.android.exoplayer.LoadControl; import com.google.android.exoplayer.SampleHolder; import com.google.android.exoplayer.SampleSource; import com.google.android.exoplayer.TrackGroup; +import com.google.android.exoplayer.TrackGroupArray; import com.google.android.exoplayer.TrackSelection; import com.google.android.exoplayer.TrackStream; import com.google.android.exoplayer.extractor.DefaultTrackOutput; @@ -78,7 +79,7 @@ public class ChunkSampleSource implements SampleSource, TrackStream, Loader.Call private long lastPerformedBufferOperation; private boolean pendingReset; - private TrackGroup[] trackGroups; + private TrackGroupArray trackGroups; private long durationUs; private Loader loader; private boolean loadingFinished; @@ -156,11 +157,11 @@ public class ChunkSampleSource implements SampleSource, TrackStream, Loader.Call } durationUs = chunkSource.getDurationUs(); TrackGroup trackGroup = chunkSource.getTracks(); - if (trackGroup.length > 0) { + if (trackGroup != null) { loader = new Loader("Loader:" + trackGroup.getFormat(0).containerMimeType); - trackGroups = new TrackGroup[] {trackGroup}; + trackGroups = new TrackGroupArray(trackGroup); } else { - trackGroups = new TrackGroup[0]; + trackGroups = new TrackGroupArray(); } state = STATE_PREPARED; return true; @@ -177,7 +178,7 @@ public class ChunkSampleSource implements SampleSource, TrackStream, Loader.Call } @Override - public TrackGroup[] getTrackGroups() { + public TrackGroupArray getTrackGroups() { Assertions.checkState(state != STATE_IDLE); return trackGroups; } diff --git a/library/src/main/java/com/google/android/exoplayer/dash/DashChunkSource.java b/library/src/main/java/com/google/android/exoplayer/dash/DashChunkSource.java index 0a78498d5f..e55d605e00 100644 --- a/library/src/main/java/com/google/android/exoplayer/dash/DashChunkSource.java +++ b/library/src/main/java/com/google/android/exoplayer/dash/DashChunkSource.java @@ -495,18 +495,20 @@ public class DashChunkSource implements ChunkSource { for (int i = 0; i < period.adaptationSets.size(); i++) { AdaptationSet adaptationSet = period.adaptationSets.get(i); if (adaptationSet.type == adaptationSetType) { - // We've found an adaptation set of the exposed type. adaptationSetIndex = i; List representations = adaptationSet.representations; - Format[] trackFormats = new Format[representations.size()]; - for (int j = 0; j < trackFormats.length; j++) { - trackFormats[j] = representations.get(j).format; + if (!representations.isEmpty()) { + // We've found a non-empty adaptation set of the exposed type. + Format[] trackFormats = new Format[representations.size()]; + for (int j = 0; j < trackFormats.length; j++) { + trackFormats[j] = representations.get(j).format; + } + trackGroup = new TrackGroup(adaptiveFormatEvaluator != null, trackFormats); + return; } - trackGroup = new TrackGroup(adaptiveFormatEvaluator != null, trackFormats); - return; } } - trackGroup = new TrackGroup(adaptiveFormatEvaluator != null); + trackGroup = null; } // Visible for testing. diff --git a/library/src/main/java/com/google/android/exoplayer/extractor/ExtractorSampleSource.java b/library/src/main/java/com/google/android/exoplayer/extractor/ExtractorSampleSource.java index 5e576650b2..f6a293d30d 100644 --- a/library/src/main/java/com/google/android/exoplayer/extractor/ExtractorSampleSource.java +++ b/library/src/main/java/com/google/android/exoplayer/extractor/ExtractorSampleSource.java @@ -21,6 +21,7 @@ import com.google.android.exoplayer.ParserException; import com.google.android.exoplayer.SampleHolder; import com.google.android.exoplayer.SampleSource; import com.google.android.exoplayer.TrackGroup; +import com.google.android.exoplayer.TrackGroupArray; import com.google.android.exoplayer.TrackSelection; import com.google.android.exoplayer.TrackStream; import com.google.android.exoplayer.drm.DrmInitData; @@ -176,7 +177,7 @@ public final class ExtractorSampleSource implements SampleSource, ExtractorOutpu private boolean prepared; private int enabledTrackCount; - private TrackGroup[] tracks; + private TrackGroupArray tracks; private long durationUs; private boolean[] pendingMediaFormat; private boolean[] pendingResets; @@ -264,7 +265,7 @@ public final class ExtractorSampleSource implements SampleSource, ExtractorOutpu if (seekMap != null && tracksBuilt && haveFormatsForAllTracks()) { int trackCount = sampleQueues.size(); - tracks = new TrackGroup[trackCount]; + TrackGroup[] tracks = new TrackGroup[trackCount]; trackEnabledStates = new boolean[trackCount]; pendingResets = new boolean[trackCount]; pendingMediaFormat = new boolean[trackCount]; @@ -272,6 +273,7 @@ public final class ExtractorSampleSource implements SampleSource, ExtractorOutpu for (int i = 0; i < trackCount; i++) { tracks[i] = new TrackGroup(sampleQueues.valueAt(i).getFormat()); } + this.tracks = new TrackGroupArray(tracks); prepared = true; return true; } @@ -290,7 +292,7 @@ public final class ExtractorSampleSource implements SampleSource, ExtractorOutpu } @Override - public TrackGroup[] getTrackGroups() { + public TrackGroupArray getTrackGroups() { return tracks; } diff --git a/library/src/main/java/com/google/android/exoplayer/hls/HlsSampleSource.java b/library/src/main/java/com/google/android/exoplayer/hls/HlsSampleSource.java index a9ab4af273..108c5afd97 100644 --- a/library/src/main/java/com/google/android/exoplayer/hls/HlsSampleSource.java +++ b/library/src/main/java/com/google/android/exoplayer/hls/HlsSampleSource.java @@ -22,6 +22,7 @@ import com.google.android.exoplayer.LoadControl; import com.google.android.exoplayer.SampleHolder; import com.google.android.exoplayer.SampleSource; import com.google.android.exoplayer.TrackGroup; +import com.google.android.exoplayer.TrackGroupArray; import com.google.android.exoplayer.TrackSelection; import com.google.android.exoplayer.TrackStream; import com.google.android.exoplayer.chunk.BaseChunkSampleSourceEventListener; @@ -79,7 +80,7 @@ public final class HlsSampleSource implements SampleSource, Loader.Callback { // Tracks are complicated in HLS. See documentation of buildTracks for details. // Indexed by track (as exposed by this source). - private TrackGroup[] trackGroups; + private TrackGroupArray trackGroups; private int primaryTrackGroupIndex; private int[] primarySelectedTracks; // Indexed by group. @@ -181,7 +182,7 @@ public final class HlsSampleSource implements SampleSource, Loader.Callback { } @Override - public TrackGroup[] getTrackGroups() { + public TrackGroupArray getTrackGroups() { Assertions.checkState(prepared); return trackGroups; } @@ -506,12 +507,12 @@ public final class HlsSampleSource implements SampleSource, Loader.Callback { // Instantiate the necessary internal data-structures. primaryTrackGroupIndex = -1; - trackGroups = new TrackGroup[extractorTrackCount]; groupEnabledStates = new boolean[extractorTrackCount]; pendingResets = new boolean[extractorTrackCount]; downstreamSampleFormats = new Format[extractorTrackCount]; // Construct the set of exposed track groups. + TrackGroup[] trackGroups = new TrackGroup[extractorTrackCount]; for (int i = 0; i < extractorTrackCount; i++) { Format sampleFormat = extractor.getSampleFormat(i); if (i == primaryExtractorTrackIndex) { @@ -525,6 +526,7 @@ public final class HlsSampleSource implements SampleSource, Loader.Callback { trackGroups[i] = new TrackGroup(sampleFormat); } } + this.trackGroups = new TrackGroupArray(trackGroups); } /** diff --git a/library/src/main/java/com/google/android/exoplayer/smoothstreaming/SmoothStreamingChunkSource.java b/library/src/main/java/com/google/android/exoplayer/smoothstreaming/SmoothStreamingChunkSource.java index 9d4f78e2aa..0a6facaff0 100644 --- a/library/src/main/java/com/google/android/exoplayer/smoothstreaming/SmoothStreamingChunkSource.java +++ b/library/src/main/java/com/google/android/exoplayer/smoothstreaming/SmoothStreamingChunkSource.java @@ -328,27 +328,29 @@ public class SmoothStreamingChunkSource implements ChunkSource { private void initForManifest(SmoothStreamingManifest manifest) { for (int i = 0; i < manifest.streamElements.length; i++) { if (manifest.streamElements[i].type == streamElementType) { - // We've found an element of the desired type. - elementIndex = i; - long timescale = manifest.streamElements[i].timescale; Format[] formats = manifest.streamElements[i].formats; - extractorWrappers = new ChunkExtractorWrapper[formats.length]; - for (int j = 0; j < formats.length; j++) { - int nalUnitLengthFieldLength = streamElementType == StreamElement.TYPE_VIDEO ? 4 : -1; - Track track = new Track(j, streamElementType, timescale, C.UNKNOWN_TIME_US, durationUs, - formats[j], trackEncryptionBoxes, nalUnitLengthFieldLength, null, null); - FragmentedMp4Extractor extractor = new FragmentedMp4Extractor( - FragmentedMp4Extractor.WORKAROUND_EVERY_VIDEO_FRAME_IS_SYNC_FRAME - | FragmentedMp4Extractor.WORKAROUND_IGNORE_TFDT_BOX); - extractor.setTrack(track); - extractorWrappers[j] = new ChunkExtractorWrapper(extractor); + if (formats.length > 0) { + // We've found an element of the desired type. + long timescale = manifest.streamElements[i].timescale; + extractorWrappers = new ChunkExtractorWrapper[formats.length]; + for (int j = 0; j < formats.length; j++) { + int nalUnitLengthFieldLength = streamElementType == StreamElement.TYPE_VIDEO ? 4 : -1; + Track track = new Track(j, streamElementType, timescale, C.UNKNOWN_TIME_US, durationUs, + formats[j], trackEncryptionBoxes, nalUnitLengthFieldLength, null, null); + FragmentedMp4Extractor extractor = new FragmentedMp4Extractor( + FragmentedMp4Extractor.WORKAROUND_EVERY_VIDEO_FRAME_IS_SYNC_FRAME + | FragmentedMp4Extractor.WORKAROUND_IGNORE_TFDT_BOX); + extractor.setTrack(track); + extractorWrappers[j] = new ChunkExtractorWrapper(extractor); + } + elementIndex = i; + trackGroup = new TrackGroup(adaptiveFormatEvaluator != null, formats); + return; } - trackGroup = new TrackGroup(adaptiveFormatEvaluator != null, formats); - return; } } extractorWrappers = null; - trackGroup = new TrackGroup(adaptiveFormatEvaluator != null); + trackGroup = null; } /**