diff --git a/demo/src/main/java/com/google/android/exoplayer2/demo/EventLogger.java b/demo/src/main/java/com/google/android/exoplayer2/demo/EventLogger.java index 5e0b76f68b..81effe14b6 100644 --- a/demo/src/main/java/com/google/android/exoplayer2/demo/EventLogger.java +++ b/demo/src/main/java/com/google/android/exoplayer2/demo/EventLogger.java @@ -40,10 +40,10 @@ import com.google.android.exoplayer2.source.AdaptiveMediaSourceEventListener; import com.google.android.exoplayer2.source.ExtractorMediaSource; import com.google.android.exoplayer2.source.TrackGroup; 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.TrackSelection; -import com.google.android.exoplayer2.trackselection.TrackSelections; -import com.google.android.exoplayer2.trackselection.TrackSelector; +import com.google.android.exoplayer2.trackselection.TrackSelectionArray; import com.google.android.exoplayer2.upstream.DataSpec; import com.google.android.exoplayer2.video.VideoRendererEventListener; import java.io.IOException; @@ -56,7 +56,7 @@ import java.util.Locale; /* package */ final class EventLogger implements ExoPlayer.EventListener, AudioRendererEventListener, VideoRendererEventListener, AdaptiveMediaSourceEventListener, ExtractorMediaSource.EventListener, StreamingDrmSessionManager.EventListener, - TrackSelector.EventListener, MetadataRenderer.Output { + MetadataRenderer.Output { private static final String TAG = "EventLogger"; private static final int MAX_TIMELINE_ITEM_LINES = 3; @@ -68,11 +68,13 @@ import java.util.Locale; TIME_FORMAT.setGroupingUsed(false); } + private final MappingTrackSelector trackSelector; private final Timeline.Window window; private final Timeline.Period period; private final long startTimeMs; - public EventLogger() { + public EventLogger(MappingTrackSelector trackSelector) { + this.trackSelector = trackSelector; window = new Timeline.Window(); period = new Timeline.Period(); startTimeMs = SystemClock.elapsedRealtime(); @@ -127,27 +129,29 @@ import java.util.Locale; Log.e(TAG, "playerFailed [" + getSessionTimeString() + "]", e); } - // MappingTrackSelector.EventListener - @Override - public void onTrackSelectionsChanged(TrackSelections trackSelections) { + public void onTracksChanged(TrackGroupArray ignored, TrackSelectionArray trackSelections) { + MappedTrackInfo mappedTrackInfo = trackSelector.getCurrentMappedTrackInfo(); + if (mappedTrackInfo == null) { + Log.d(TAG, "Tracks []"); + return; + } Log.d(TAG, "Tracks ["); // Log tracks associated to renderers. - MappedTrackInfo info = trackSelections.info; - for (int rendererIndex = 0; rendererIndex < trackSelections.length; rendererIndex++) { - TrackGroupArray trackGroups = info.getTrackGroups(rendererIndex); + for (int rendererIndex = 0; rendererIndex < mappedTrackInfo.length; rendererIndex++) { + TrackGroupArray rendererTrackGroups = mappedTrackInfo.getTrackGroups(rendererIndex); TrackSelection trackSelection = trackSelections.get(rendererIndex); - if (trackGroups.length > 0) { + if (rendererTrackGroups.length > 0) { Log.d(TAG, " Renderer:" + rendererIndex + " ["); - for (int groupIndex = 0; groupIndex < trackGroups.length; groupIndex++) { - TrackGroup trackGroup = trackGroups.get(groupIndex); - String adaptiveSupport = getAdaptiveSupportString( - trackGroup.length, info.getAdaptiveSupport(rendererIndex, groupIndex, false)); + for (int groupIndex = 0; groupIndex < rendererTrackGroups.length; groupIndex++) { + TrackGroup trackGroup = rendererTrackGroups.get(groupIndex); + String adaptiveSupport = getAdaptiveSupportString(trackGroup.length, + mappedTrackInfo.getAdaptiveSupport(rendererIndex, groupIndex, false)); Log.d(TAG, " Group:" + groupIndex + ", adaptive_supported=" + adaptiveSupport + " ["); for (int trackIndex = 0; trackIndex < trackGroup.length; trackIndex++) { String status = getTrackStatusString(trackSelection, trackGroup, trackIndex); String formatSupport = getFormatSupportString( - info.getTrackFormatSupport(rendererIndex, groupIndex, trackIndex)); + mappedTrackInfo.getTrackFormatSupport(rendererIndex, groupIndex, trackIndex)); Log.d(TAG, " " + status + " Track:" + trackIndex + ", " + getFormatString(trackGroup.getFormat(trackIndex)) + ", supported=" + formatSupport); @@ -170,12 +174,12 @@ import java.util.Locale; } } // Log tracks not associated with a renderer. - TrackGroupArray trackGroups = info.getUnassociatedTrackGroups(); - if (trackGroups.length > 0) { + TrackGroupArray unassociatedTrackGroups = mappedTrackInfo.getUnassociatedTrackGroups(); + if (unassociatedTrackGroups.length > 0) { Log.d(TAG, " Renderer:None ["); - for (int groupIndex = 0; groupIndex < trackGroups.length; groupIndex++) { + for (int groupIndex = 0; groupIndex < unassociatedTrackGroups.length; groupIndex++) { Log.d(TAG, " Group:" + groupIndex + " ["); - TrackGroup trackGroup = trackGroups.get(groupIndex); + TrackGroup trackGroup = unassociatedTrackGroups.get(groupIndex); for (int trackIndex = 0; trackIndex < trackGroup.length; trackIndex++) { String status = getTrackStatusString(false); String formatSupport = getFormatSupportString( diff --git a/demo/src/main/java/com/google/android/exoplayer2/demo/PlayerActivity.java b/demo/src/main/java/com/google/android/exoplayer2/demo/PlayerActivity.java index e9aa46f85f..5351890d6f 100644 --- a/demo/src/main/java/com/google/android/exoplayer2/demo/PlayerActivity.java +++ b/demo/src/main/java/com/google/android/exoplayer2/demo/PlayerActivity.java @@ -58,8 +58,7 @@ import com.google.android.exoplayer2.trackselection.DefaultTrackSelector; import com.google.android.exoplayer2.trackselection.MappingTrackSelector; import com.google.android.exoplayer2.trackselection.MappingTrackSelector.MappedTrackInfo; import com.google.android.exoplayer2.trackselection.TrackSelection; -import com.google.android.exoplayer2.trackselection.TrackSelections; -import com.google.android.exoplayer2.trackselection.TrackSelector; +import com.google.android.exoplayer2.trackselection.TrackSelectionArray; import com.google.android.exoplayer2.ui.DebugTextViewHelper; import com.google.android.exoplayer2.ui.PlaybackControlView; import com.google.android.exoplayer2.ui.SimpleExoPlayerView; @@ -78,7 +77,7 @@ import java.util.UUID; * An activity that plays media using {@link SimpleExoPlayer}. */ public class PlayerActivity extends Activity implements OnClickListener, ExoPlayer.EventListener, - TrackSelector.EventListener, PlaybackControlView.VisibilityListener { + PlaybackControlView.VisibilityListener { public static final String DRM_SCHEME_UUID_EXTRA = "drm_scheme_uuid"; public static final String DRM_LICENSE_URL = "drm_license_url"; @@ -203,8 +202,11 @@ public class PlayerActivity extends Activity implements OnClickListener, ExoPlay if (view == retryButton) { initializePlayer(); } else if (view.getParent() == debugRootView) { - trackSelectionHelper.showSelectionDialog(this, ((Button) view).getText(), - trackSelector.getCurrentSelections().info, (int) view.getTag()); + MappedTrackInfo mappedTrackInfo = trackSelector.getCurrentMappedTrackInfo(); + if (mappedTrackInfo != null) { + trackSelectionHelper.showSelectionDialog(this, ((Button) view).getText(), + trackSelector.getCurrentMappedTrackInfo(), (int) view.getTag()); + } } } @@ -249,20 +251,20 @@ public class PlayerActivity extends Activity implements OnClickListener, ExoPlay } } - eventLogger = new EventLogger(); TrackSelection.Factory videoTrackSelectionFactory = new AdaptiveVideoTrackSelection.Factory(BANDWIDTH_METER); - trackSelector = new DefaultTrackSelector(mainHandler, videoTrackSelectionFactory); - trackSelector.addListener(this); - trackSelector.addListener(eventLogger); + trackSelector = new DefaultTrackSelector(videoTrackSelectionFactory); trackSelectionHelper = new TrackSelectionHelper(trackSelector, videoTrackSelectionFactory); player = ExoPlayerFactory.newSimpleInstance(this, trackSelector, new DefaultLoadControl(), drmSessionManager, preferExtensionDecoders); player.addListener(this); + + eventLogger = new EventLogger(trackSelector); player.addListener(eventLogger); player.setAudioDebugListener(eventLogger); player.setVideoDebugListener(eventLogger); player.setId3Output(eventLogger); + simpleExoPlayerView.setPlayer(player); if (isTimelineStatic) { if (playerPosition == C.TIME_UNSET) { @@ -447,17 +449,17 @@ public class PlayerActivity extends Activity implements OnClickListener, ExoPlay showControls(); } - // MappingTrackSelector.EventListener implementation - @Override - public void onTrackSelectionsChanged(TrackSelections trackSelections) { + public void onTracksChanged(TrackGroupArray trackGroups, TrackSelectionArray trackSelections) { updateButtonVisibilities(); - MappedTrackInfo trackInfo = trackSelections.info; - if (trackInfo.hasOnlyUnplayableTracks(C.TRACK_TYPE_VIDEO)) { - showToast(R.string.error_unsupported_video); - } - if (trackInfo.hasOnlyUnplayableTracks(C.TRACK_TYPE_AUDIO)) { - showToast(R.string.error_unsupported_audio); + MappedTrackInfo mappedTrackInfo = trackSelector.getCurrentMappedTrackInfo(); + if (mappedTrackInfo != null) { + if (mappedTrackInfo.hasOnlyUnplayableTracks(C.TRACK_TYPE_VIDEO)) { + showToast(R.string.error_unsupported_video); + } + if (mappedTrackInfo.hasOnlyUnplayableTracks(C.TRACK_TYPE_AUDIO)) { + showToast(R.string.error_unsupported_audio); + } } } @@ -473,14 +475,13 @@ public class PlayerActivity extends Activity implements OnClickListener, ExoPlay return; } - TrackSelections trackSelections = trackSelector.getCurrentSelections(); - if (trackSelections == null) { + MappedTrackInfo mappedTrackInfo = trackSelector.getCurrentMappedTrackInfo(); + if (mappedTrackInfo == null) { return; } - int rendererCount = trackSelections.length; - for (int i = 0; i < rendererCount; i++) { - TrackGroupArray trackGroups = trackSelections.info.getTrackGroups(i); + for (int i = 0; i < mappedTrackInfo.length; i++) { + TrackGroupArray trackGroups = mappedTrackInfo.getTrackGroups(i); if (trackGroups.length != 0) { Button button = new Button(this); int label; diff --git a/extensions/flac/src/androidTest/java/com/google/android/exoplayer2/ext/flac/FlacPlaybackTest.java b/extensions/flac/src/androidTest/java/com/google/android/exoplayer2/ext/flac/FlacPlaybackTest.java index 29a22f380a..990c470a93 100644 --- a/extensions/flac/src/androidTest/java/com/google/android/exoplayer2/ext/flac/FlacPlaybackTest.java +++ b/extensions/flac/src/androidTest/java/com/google/android/exoplayer2/ext/flac/FlacPlaybackTest.java @@ -17,7 +17,6 @@ package com.google.android.exoplayer2.ext.flac; import android.content.Context; import android.net.Uri; -import android.os.Handler; import android.os.Looper; import android.test.InstrumentationTestCase; import com.google.android.exoplayer2.ExoPlaybackException; @@ -27,7 +26,9 @@ import com.google.android.exoplayer2.Renderer; import com.google.android.exoplayer2.Timeline; import com.google.android.exoplayer2.extractor.mkv.MatroskaExtractor; import com.google.android.exoplayer2.source.ExtractorMediaSource; +import com.google.android.exoplayer2.source.TrackGroupArray; import com.google.android.exoplayer2.trackselection.DefaultTrackSelector; +import com.google.android.exoplayer2.trackselection.TrackSelectionArray; import com.google.android.exoplayer2.upstream.DefaultDataSourceFactory; /** @@ -72,7 +73,7 @@ public class FlacPlaybackTest extends InstrumentationTestCase { public void run() { Looper.prepare(); LibflacAudioRenderer audioRenderer = new LibflacAudioRenderer(); - DefaultTrackSelector trackSelector = new DefaultTrackSelector(new Handler()); + DefaultTrackSelector trackSelector = new DefaultTrackSelector(); player = ExoPlayerFactory.newInstance(new Renderer[] {audioRenderer}, trackSelector); player.addListener(this); ExtractorMediaSource mediaSource = new ExtractorMediaSource( @@ -91,6 +92,11 @@ public class FlacPlaybackTest extends InstrumentationTestCase { // Do nothing. } + @Override + public void onTracksChanged(TrackGroupArray trackGroups, TrackSelectionArray trackSelections) { + // Do nothing. + } + @Override public void onPositionDiscontinuity() { // Do nothing. diff --git a/extensions/opus/src/androidTest/java/com/google/android/exoplayer2/ext/opus/OpusPlaybackTest.java b/extensions/opus/src/androidTest/java/com/google/android/exoplayer2/ext/opus/OpusPlaybackTest.java index 4f723698a4..3e07186995 100644 --- a/extensions/opus/src/androidTest/java/com/google/android/exoplayer2/ext/opus/OpusPlaybackTest.java +++ b/extensions/opus/src/androidTest/java/com/google/android/exoplayer2/ext/opus/OpusPlaybackTest.java @@ -17,7 +17,6 @@ package com.google.android.exoplayer2.ext.opus; import android.content.Context; import android.net.Uri; -import android.os.Handler; import android.os.Looper; import android.test.InstrumentationTestCase; import com.google.android.exoplayer2.ExoPlaybackException; @@ -27,7 +26,9 @@ import com.google.android.exoplayer2.Renderer; import com.google.android.exoplayer2.Timeline; import com.google.android.exoplayer2.extractor.mkv.MatroskaExtractor; import com.google.android.exoplayer2.source.ExtractorMediaSource; +import com.google.android.exoplayer2.source.TrackGroupArray; import com.google.android.exoplayer2.trackselection.DefaultTrackSelector; +import com.google.android.exoplayer2.trackselection.TrackSelectionArray; import com.google.android.exoplayer2.upstream.DefaultDataSourceFactory; /** @@ -72,7 +73,7 @@ public class OpusPlaybackTest extends InstrumentationTestCase { public void run() { Looper.prepare(); LibopusAudioRenderer audioRenderer = new LibopusAudioRenderer(); - DefaultTrackSelector trackSelector = new DefaultTrackSelector(new Handler()); + DefaultTrackSelector trackSelector = new DefaultTrackSelector(); player = ExoPlayerFactory.newInstance(new Renderer[] {audioRenderer}, trackSelector); player.addListener(this); ExtractorMediaSource mediaSource = new ExtractorMediaSource( @@ -91,6 +92,11 @@ public class OpusPlaybackTest extends InstrumentationTestCase { // Do nothing. } + @Override + public void onTracksChanged(TrackGroupArray trackGroups, TrackSelectionArray trackSelections) { + // Do nothing. + } + @Override public void onPositionDiscontinuity() { // Do nothing. diff --git a/extensions/vp9/src/androidTest/java/com/google/android/exoplayer2/ext/vp9/VpxPlaybackTest.java b/extensions/vp9/src/androidTest/java/com/google/android/exoplayer2/ext/vp9/VpxPlaybackTest.java index c5f61cf231..b1ddf2368c 100644 --- a/extensions/vp9/src/androidTest/java/com/google/android/exoplayer2/ext/vp9/VpxPlaybackTest.java +++ b/extensions/vp9/src/androidTest/java/com/google/android/exoplayer2/ext/vp9/VpxPlaybackTest.java @@ -17,7 +17,6 @@ package com.google.android.exoplayer2.ext.vp9; import android.content.Context; import android.net.Uri; -import android.os.Handler; import android.os.Looper; import android.test.InstrumentationTestCase; import com.google.android.exoplayer2.ExoPlaybackException; @@ -27,7 +26,9 @@ import com.google.android.exoplayer2.Renderer; import com.google.android.exoplayer2.Timeline; import com.google.android.exoplayer2.extractor.mkv.MatroskaExtractor; import com.google.android.exoplayer2.source.ExtractorMediaSource; +import com.google.android.exoplayer2.source.TrackGroupArray; import com.google.android.exoplayer2.trackselection.DefaultTrackSelector; +import com.google.android.exoplayer2.trackselection.TrackSelectionArray; import com.google.android.exoplayer2.upstream.DefaultDataSourceFactory; /** @@ -88,7 +89,7 @@ public class VpxPlaybackTest extends InstrumentationTestCase { public void run() { Looper.prepare(); LibvpxVideoRenderer videoRenderer = new LibvpxVideoRenderer(true, 0); - DefaultTrackSelector trackSelector = new DefaultTrackSelector(new Handler()); + DefaultTrackSelector trackSelector = new DefaultTrackSelector(); player = ExoPlayerFactory.newInstance(new Renderer[] {videoRenderer}, trackSelector); player.addListener(this); ExtractorMediaSource mediaSource = new ExtractorMediaSource( @@ -110,6 +111,11 @@ public class VpxPlaybackTest extends InstrumentationTestCase { // Do nothing. } + @Override + public void onTracksChanged(TrackGroupArray trackGroups, TrackSelectionArray trackSelections) { + // Do nothing. + } + @Override public void onPositionDiscontinuity() { // Do nothing. diff --git a/library/src/androidTest/assets/ts/sample_with_sdt.ts b/library/src/androidTest/assets/ts/sample_with_sdt.ts new file mode 100644 index 0000000000..8d1eccee15 Binary files /dev/null and b/library/src/androidTest/assets/ts/sample_with_sdt.ts differ diff --git a/library/src/androidTest/java/com/google/android/exoplayer2/extractor/ts/TsExtractorTest.java b/library/src/androidTest/java/com/google/android/exoplayer2/extractor/ts/TsExtractorTest.java index a455a3b841..58893f15c1 100644 --- a/library/src/androidTest/java/com/google/android/exoplayer2/extractor/ts/TsExtractorTest.java +++ b/library/src/androidTest/java/com/google/android/exoplayer2/extractor/ts/TsExtractorTest.java @@ -16,6 +16,7 @@ package com.google.android.exoplayer2.extractor.ts; import android.test.InstrumentationTestCase; +import android.util.SparseArray; import com.google.android.exoplayer2.Format; import com.google.android.exoplayer2.extractor.Extractor; import com.google.android.exoplayer2.extractor.ExtractorOutput; @@ -73,7 +74,7 @@ public final class TsExtractorTest extends InstrumentationTestCase { } public void testCustomPesReader() throws Exception { - CustomEsReaderFactory factory = new CustomEsReaderFactory(); + CustomTsPayloadReaderFactory factory = new CustomTsPayloadReaderFactory(true, false); TsExtractor tsExtractor = new TsExtractor(new TimestampAdjuster(0), factory, false); FakeExtractorInput input = new FakeExtractorInput.Builder() .setData(TestUtil.getByteArray(getInstrumentation(), "ts/sample.ts")) @@ -82,13 +83,12 @@ public final class TsExtractorTest extends InstrumentationTestCase { .setSimulatePartialReads(false).build(); FakeExtractorOutput output = new FakeExtractorOutput(); tsExtractor.init(output); - tsExtractor.seek(input.getPosition()); PositionHolder seekPositionHolder = new PositionHolder(); int readResult = Extractor.RESULT_CONTINUE; while (readResult != Extractor.RESULT_END_OF_INPUT) { readResult = tsExtractor.read(input, seekPositionHolder); } - CustomEsReader reader = factory.reader; + CustomEsReader reader = factory.esReader; assertEquals(2, reader.packetsRead); TrackOutput trackOutput = reader.getTrackOutput(); assertTrue(trackOutput == output.trackOutputs.get(257 /* PID of audio track. */)); @@ -97,6 +97,23 @@ public final class TsExtractorTest extends InstrumentationTestCase { ((FakeTrackOutput) trackOutput).format); } + public void testCustomInitialSectionReader() throws Exception { + CustomTsPayloadReaderFactory factory = new CustomTsPayloadReaderFactory(false, true); + TsExtractor tsExtractor = new TsExtractor(new TimestampAdjuster(0), factory, false); + FakeExtractorInput input = new FakeExtractorInput.Builder() + .setData(TestUtil.getByteArray(getInstrumentation(), "ts/sample_with_sdt.ts")) + .setSimulateIOErrors(false) + .setSimulateUnknownLength(false) + .setSimulatePartialReads(false).build(); + tsExtractor.init(new FakeExtractorOutput()); + PositionHolder seekPositionHolder = new PositionHolder(); + int readResult = Extractor.RESULT_CONTINUE; + while (readResult != Extractor.RESULT_END_OF_INPUT) { + readResult = tsExtractor.read(input, seekPositionHolder); + } + assertEquals(1, factory.sdtReader.consumedSdts); + } + private static void writeJunkData(ByteArrayOutputStream out, int length) throws IOException { for (int i = 0; i < length; i++) { if (((byte) i) == TS_SYNC_BYTE) { @@ -107,6 +124,45 @@ public final class TsExtractorTest extends InstrumentationTestCase { } } + private static final class CustomTsPayloadReaderFactory implements TsPayloadReader.Factory { + + private final boolean provideSdtReader; + private final boolean provideCustomEsReader; + private final TsPayloadReader.Factory defaultFactory; + private CustomEsReader esReader; + private SdtSectionReader sdtReader; + + public CustomTsPayloadReaderFactory(boolean provideCustomEsReader, boolean provideSdtReader) { + this.provideCustomEsReader = provideCustomEsReader; + this.provideSdtReader = provideSdtReader; + defaultFactory = new DefaultTsPayloadReaderFactory(); + } + + @Override + public SparseArray createInitialPayloadReaders() { + if (provideSdtReader) { + assertNull(sdtReader); + SparseArray mapping = new SparseArray<>(); + sdtReader = new SdtSectionReader(); + mapping.put(17, new SectionReader(sdtReader)); + return mapping; + } else { + return defaultFactory.createInitialPayloadReaders(); + } + } + + @Override + public TsPayloadReader createPayloadReader(int streamType, EsInfo esInfo) { + if (provideCustomEsReader && streamType == 3) { + esReader = new CustomEsReader(esInfo.language); + return new PesReader(esReader); + } else { + return defaultFactory.createPayloadReader(streamType, esInfo); + } + } + + } + private static final class CustomEsReader implements ElementaryStreamReader { private final String language; @@ -147,23 +203,44 @@ public final class TsExtractorTest extends InstrumentationTestCase { } - private static final class CustomEsReaderFactory implements TsPayloadReader.Factory { + private static final class SdtSectionReader implements SectionPayloadReader { - private final TsPayloadReader.Factory defaultFactory; - private CustomEsReader reader; + private int consumedSdts; - public CustomEsReaderFactory() { - defaultFactory = new DefaultTsPayloadReaderFactory(); + @Override + public void init(TimestampAdjuster timestampAdjuster, ExtractorOutput extractorOutput, + TrackIdGenerator idGenerator) { + // Do nothing. } @Override - public TsPayloadReader createPayloadReader(int streamType, EsInfo esInfo) { - if (streamType == 3) { - reader = new CustomEsReader(esInfo.language); - return new PesReader(reader); - } else { - return defaultFactory.createPayloadReader(streamType, esInfo); + public void consume(ParsableByteArray sectionData) { + // table_id(8), section_syntax_indicator(1), reserved_future_use(1), reserved(2), + // section_length(12), transport_stream_id(16), reserved(2), version_number(5), + // current_next_indicator(1), section_number(8), last_section_number(8), + // original_network_id(16), reserved_future_use(8) + sectionData.skipBytes(11); + // Start of the service loop. + assertEquals(0x5566 /* arbitrary service id */, sectionData.readUnsignedShort()); + // reserved_future_use(6), EIT_schedule_flag(1), EIT_present_following_flag(1) + sectionData.skipBytes(1); + // Assert there is only one service. + // Remove running_status(3), free_CA_mode(1) from the descriptors_loop_length with the mask. + assertEquals(sectionData.readUnsignedShort() & 0xFFF, sectionData.bytesLeft()); + while (sectionData.bytesLeft() > 0) { + int descriptorTag = sectionData.readUnsignedByte(); + int descriptorLength = sectionData.readUnsignedByte(); + if (descriptorTag == 72 /* service descriptor */) { + assertEquals(1, sectionData.readUnsignedByte()); // Service type: Digital TV. + int serviceProviderNameLength = sectionData.readUnsignedByte(); + assertEquals("Some provider", sectionData.readString(serviceProviderNameLength)); + int serviceNameLength = sectionData.readUnsignedByte(); + assertEquals("Some Channel", sectionData.readString(serviceNameLength)); + } else { + sectionData.skipBytes(descriptorLength); + } } + consumedSdts++; } } diff --git a/library/src/main/java/com/google/android/exoplayer2/DefaultLoadControl.java b/library/src/main/java/com/google/android/exoplayer2/DefaultLoadControl.java index e7a0f8b1b8..e6a39d8a27 100644 --- a/library/src/main/java/com/google/android/exoplayer2/DefaultLoadControl.java +++ b/library/src/main/java/com/google/android/exoplayer2/DefaultLoadControl.java @@ -16,7 +16,7 @@ package com.google.android.exoplayer2; import com.google.android.exoplayer2.source.TrackGroupArray; -import com.google.android.exoplayer2.trackselection.TrackSelections; +import com.google.android.exoplayer2.trackselection.TrackSelectionArray; import com.google.android.exoplayer2.upstream.Allocator; import com.google.android.exoplayer2.upstream.DefaultAllocator; import com.google.android.exoplayer2.util.Util; @@ -111,7 +111,7 @@ public final class DefaultLoadControl implements LoadControl { @Override public void onTracksSelected(Renderer[] renderers, TrackGroupArray trackGroups, - TrackSelections trackSelections) { + TrackSelectionArray trackSelections) { targetBufferSize = 0; for (int i = 0; i < renderers.length; i++) { if (trackSelections.get(i) != null) { diff --git a/library/src/main/java/com/google/android/exoplayer2/ExoPlayer.java b/library/src/main/java/com/google/android/exoplayer2/ExoPlayer.java index e3c9b6e114..31efdd82b1 100644 --- a/library/src/main/java/com/google/android/exoplayer2/ExoPlayer.java +++ b/library/src/main/java/com/google/android/exoplayer2/ExoPlayer.java @@ -22,11 +22,13 @@ import com.google.android.exoplayer2.source.ExtractorMediaSource; import com.google.android.exoplayer2.source.MediaSource; import com.google.android.exoplayer2.source.MergingMediaSource; import com.google.android.exoplayer2.source.SingleSampleMediaSource; +import com.google.android.exoplayer2.source.TrackGroupArray; import com.google.android.exoplayer2.source.dash.DashMediaSource; import com.google.android.exoplayer2.source.hls.HlsMediaSource; import com.google.android.exoplayer2.source.smoothstreaming.SsMediaSource; import com.google.android.exoplayer2.text.TextRenderer; import com.google.android.exoplayer2.trackselection.DefaultTrackSelector; +import com.google.android.exoplayer2.trackselection.TrackSelectionArray; import com.google.android.exoplayer2.trackselection.TrackSelector; import com.google.android.exoplayer2.upstream.DataSource; import com.google.android.exoplayer2.video.MediaCodecVideoRenderer; @@ -110,6 +112,15 @@ public interface ExoPlayer { */ interface EventListener { + /** + * Called when the available or selected tracks change. + * + * @param trackGroups The available tracks. Never null, but may be of length zero. + * @param trackSelections The track selections for each {@link Renderer}. Never null and always + * of length {@link #getRendererCount()}, but may contain null elements. + */ + void onTracksChanged(TrackGroupArray trackGroups, TrackSelectionArray trackSelections); + /** * Called when the player starts or stops loading the source. * @@ -259,11 +270,11 @@ public interface ExoPlayer { * @param resetPosition Whether the playback position should be reset to the default position in * the first {@link Timeline.Window}. If false, playback will start from the position defined * by {@link #getCurrentWindowIndex()} and {@link #getCurrentPosition()}. - * @param resetTimeline Whether the timeline and manifest should be reset. Should be true unless - * the player is being prepared to play the same media as it was playing previously (e.g. if - * playback failed and is being retried). + * @param resetState Whether the timeline, manifest, tracks and track selections should be reset. + * Should be true unless the player is being prepared to play the same media as it was playing + * previously (e.g. if playback failed and is being retried). */ - void prepare(MediaSource mediaSource, boolean resetPosition, boolean resetTimeline); + void prepare(MediaSource mediaSource, boolean resetPosition, boolean resetState); /** * Sets whether playback should proceed when {@link #getPlaybackState()} == {@link #STATE_READY}. @@ -356,6 +367,30 @@ public interface ExoPlayer { */ void blockingSendMessages(ExoPlayerMessage... messages); + /** + * Returns the number of renderers. + */ + int getRendererCount(); + + /** + * Returns the track type that the renderer at a given index handles. + * + * @see Renderer#getTrackType() + * @param index The index of the renderer. + * @return One of the {@code TRACK_TYPE_*} constants defined in {@link C}. + */ + int getRendererType(int index); + + /** + * Returns the available track groups. + */ + TrackGroupArray getCurrentTrackGroups(); + + /** + * Returns the current track selections for each renderer. + */ + TrackSelectionArray getCurrentTrackSelections(); + /** * Returns the current manifest. The type depends on the {@link MediaSource} passed to * {@link #prepare}. diff --git a/library/src/main/java/com/google/android/exoplayer2/ExoPlayerFactory.java b/library/src/main/java/com/google/android/exoplayer2/ExoPlayerFactory.java index 91ab56805a..e43a9c0357 100644 --- a/library/src/main/java/com/google/android/exoplayer2/ExoPlayerFactory.java +++ b/library/src/main/java/com/google/android/exoplayer2/ExoPlayerFactory.java @@ -42,7 +42,7 @@ public final class ExoPlayerFactory { * @param trackSelector The {@link TrackSelector} that will be used by the instance. * @param loadControl The {@link LoadControl} that will be used by the instance. */ - public static SimpleExoPlayer newSimpleInstance(Context context, TrackSelector trackSelector, + public static SimpleExoPlayer newSimpleInstance(Context context, TrackSelector trackSelector, LoadControl loadControl) { return newSimpleInstance(context, trackSelector, loadControl, null); } @@ -57,7 +57,7 @@ public final class ExoPlayerFactory { * @param drmSessionManager An optional {@link DrmSessionManager}. May be null if the instance * will not be used for DRM protected playbacks. */ - public static SimpleExoPlayer newSimpleInstance(Context context, TrackSelector trackSelector, + public static SimpleExoPlayer newSimpleInstance(Context context, TrackSelector trackSelector, LoadControl loadControl, DrmSessionManager drmSessionManager) { return newSimpleInstance(context, trackSelector, loadControl, drmSessionManager, false); } @@ -75,7 +75,7 @@ public final class ExoPlayerFactory { * available extensions over those defined in the core library. Note that extensions must be * included in the application build for setting this flag to have any effect. */ - public static SimpleExoPlayer newSimpleInstance(Context context, TrackSelector trackSelector, + public static SimpleExoPlayer newSimpleInstance(Context context, TrackSelector trackSelector, LoadControl loadControl, DrmSessionManager drmSessionManager, boolean preferExtensionDecoders) { return newSimpleInstance(context, trackSelector, loadControl, drmSessionManager, @@ -97,7 +97,7 @@ public final class ExoPlayerFactory { * @param allowedVideoJoiningTimeMs The maximum duration for which a video renderer can attempt to * seamlessly join an ongoing playback. */ - public static SimpleExoPlayer newSimpleInstance(Context context, TrackSelector trackSelector, + public static SimpleExoPlayer newSimpleInstance(Context context, TrackSelector trackSelector, LoadControl loadControl, DrmSessionManager drmSessionManager, boolean preferExtensionDecoders, long allowedVideoJoiningTimeMs) { return new SimpleExoPlayer(context, trackSelector, loadControl, drmSessionManager, @@ -111,7 +111,7 @@ public final class ExoPlayerFactory { * @param renderers The {@link Renderer}s that will be used by the instance. * @param trackSelector The {@link TrackSelector} that will be used by the instance. */ - public static ExoPlayer newInstance(Renderer[] renderers, TrackSelector trackSelector) { + public static ExoPlayer newInstance(Renderer[] renderers, TrackSelector trackSelector) { return newInstance(renderers, trackSelector, new DefaultLoadControl()); } @@ -123,7 +123,7 @@ public final class ExoPlayerFactory { * @param trackSelector The {@link TrackSelector} that will be used by the instance. * @param loadControl The {@link LoadControl} that will be used by the instance. */ - public static ExoPlayer newInstance(Renderer[] renderers, TrackSelector trackSelector, + public static ExoPlayer newInstance(Renderer[] renderers, TrackSelector trackSelector, LoadControl loadControl) { return new ExoPlayerImpl(renderers, trackSelector, loadControl); } diff --git a/library/src/main/java/com/google/android/exoplayer2/ExoPlayerImpl.java b/library/src/main/java/com/google/android/exoplayer2/ExoPlayerImpl.java index 3eb2ceb38b..2e0502c019 100644 --- a/library/src/main/java/com/google/android/exoplayer2/ExoPlayerImpl.java +++ b/library/src/main/java/com/google/android/exoplayer2/ExoPlayerImpl.java @@ -22,7 +22,11 @@ import android.os.Message; import android.util.Log; import android.util.Pair; import com.google.android.exoplayer2.ExoPlayerImplInternal.PlaybackInfo; +import com.google.android.exoplayer2.ExoPlayerImplInternal.TrackInfo; import com.google.android.exoplayer2.source.MediaSource; +import com.google.android.exoplayer2.source.TrackGroupArray; +import com.google.android.exoplayer2.trackselection.TrackSelection; +import com.google.android.exoplayer2.trackselection.TrackSelectionArray; import com.google.android.exoplayer2.trackselection.TrackSelector; import com.google.android.exoplayer2.util.Assertions; import java.util.concurrent.CopyOnWriteArraySet; @@ -34,12 +38,16 @@ import java.util.concurrent.CopyOnWriteArraySet; private static final String TAG = "ExoPlayerImpl"; + private final Renderer[] renderers; + private final TrackSelector trackSelector; + private final TrackSelectionArray emptyTrackSelections; private final Handler eventHandler; - private final ExoPlayerImplInternal internalPlayer; + private final ExoPlayerImplInternal internalPlayer; private final CopyOnWriteArraySet listeners; private final Timeline.Window window; private final Timeline.Period period; + private boolean tracksSelected; private boolean pendingInitialSeek; private boolean playWhenReady; private int playbackState; @@ -47,6 +55,8 @@ import java.util.concurrent.CopyOnWriteArraySet; private boolean isLoading; private Timeline timeline; private Object manifest; + private TrackGroupArray trackGroups; + private TrackSelectionArray trackSelections; // Playback information when there is no pending seek/set source operation. private PlaybackInfo playbackInfo; @@ -63,16 +73,19 @@ import java.util.concurrent.CopyOnWriteArraySet; * @param loadControl The {@link LoadControl} that will be used by the instance. */ @SuppressLint("HandlerLeak") - public ExoPlayerImpl(Renderer[] renderers, TrackSelector trackSelector, - LoadControl loadControl) { + public ExoPlayerImpl(Renderer[] renderers, TrackSelector trackSelector, LoadControl loadControl) { Log.i(TAG, "Init " + ExoPlayerLibraryInfo.VERSION); - Assertions.checkNotNull(renderers); Assertions.checkState(renderers.length > 0); + this.renderers = Assertions.checkNotNull(renderers); + this.trackSelector = Assertions.checkNotNull(trackSelector); this.playWhenReady = false; this.playbackState = STATE_IDLE; this.listeners = new CopyOnWriteArraySet<>(); + emptyTrackSelections = new TrackSelectionArray(new TrackSelection[renderers.length]); window = new Timeline.Window(); period = new Timeline.Period(); + trackGroups = TrackGroupArray.EMPTY; + trackSelections = emptyTrackSelections; eventHandler = new Handler() { @Override public void handleMessage(Message msg) { @@ -80,8 +93,8 @@ import java.util.concurrent.CopyOnWriteArraySet; } }; playbackInfo = new ExoPlayerImplInternal.PlaybackInfo(0, 0); - internalPlayer = new ExoPlayerImplInternal<>(renderers, trackSelector, loadControl, - playWhenReady, eventHandler, playbackInfo); + internalPlayer = new ExoPlayerImplInternal(renderers, trackSelector, loadControl, playWhenReady, + eventHandler, playbackInfo); } @Override @@ -105,12 +118,23 @@ import java.util.concurrent.CopyOnWriteArraySet; } @Override - public void prepare(MediaSource mediaSource, boolean resetPosition, boolean resetTimeline) { - if (resetTimeline && (timeline != null || manifest != null)) { - timeline = null; - manifest = null; - for (EventListener listener : listeners) { - listener.onTimelineChanged(null, null); + public void prepare(MediaSource mediaSource, boolean resetPosition, boolean resetState) { + if (resetState) { + if (timeline != null || manifest != null) { + timeline = null; + manifest = null; + for (EventListener listener : listeners) { + listener.onTimelineChanged(null, null); + } + } + if (tracksSelected) { + tracksSelected = false; + trackGroups = TrackGroupArray.EMPTY; + trackSelections = emptyTrackSelections; + trackSelector.onSelectionActivated(null); + for (EventListener listener : listeners) { + listener.onTracksChanged(trackGroups, trackSelections); + } } } internalPlayer.prepare(mediaSource, resetPosition); @@ -266,6 +290,26 @@ import java.util.concurrent.CopyOnWriteArraySet; : (int) (duration == 0 ? 100 : (bufferedPosition * 100) / duration); } + @Override + public int getRendererCount() { + return renderers.length; + } + + @Override + public int getRendererType(int index) { + return renderers[index].getTrackType(); + } + + @Override + public TrackGroupArray getCurrentTrackGroups() { + return trackGroups; + } + + @Override + public TrackSelectionArray getCurrentTrackSelections() { + return trackSelections; + } + @Override public Timeline getCurrentTimeline() { return timeline; @@ -293,6 +337,17 @@ import java.util.concurrent.CopyOnWriteArraySet; } break; } + case ExoPlayerImplInternal.MSG_TRACKS_CHANGED: { + TrackInfo trackInfo = (TrackInfo) msg.obj; + tracksSelected = true; + trackGroups = trackInfo.groups; + trackSelections = trackInfo.selections; + trackSelector.onSelectionActivated(trackInfo.info); + for (EventListener listener : listeners) { + listener.onTracksChanged(trackGroups, trackSelections); + } + break; + } case ExoPlayerImplInternal.MSG_SEEK_ACK: { if (--pendingSeekAcks == 0) { playbackInfo = (ExoPlayerImplInternal.PlaybackInfo) msg.obj; diff --git a/library/src/main/java/com/google/android/exoplayer2/ExoPlayerImplInternal.java b/library/src/main/java/com/google/android/exoplayer2/ExoPlayerImplInternal.java index 9d6c435635..6d8f3af0c3 100644 --- a/library/src/main/java/com/google/android/exoplayer2/ExoPlayerImplInternal.java +++ b/library/src/main/java/com/google/android/exoplayer2/ExoPlayerImplInternal.java @@ -26,8 +26,9 @@ import com.google.android.exoplayer2.ExoPlayer.ExoPlayerMessage; import com.google.android.exoplayer2.source.MediaPeriod; import com.google.android.exoplayer2.source.MediaSource; import com.google.android.exoplayer2.source.SampleStream; +import com.google.android.exoplayer2.source.TrackGroupArray; import com.google.android.exoplayer2.trackselection.TrackSelection; -import com.google.android.exoplayer2.trackselection.TrackSelections; +import com.google.android.exoplayer2.trackselection.TrackSelectionArray; import com.google.android.exoplayer2.trackselection.TrackSelector; import com.google.android.exoplayer2.util.Assertions; import com.google.android.exoplayer2.util.MediaClock; @@ -40,7 +41,7 @@ import java.io.IOException; /** * Implements the internal behavior of {@link ExoPlayerImpl}. */ -/* package */ final class ExoPlayerImplInternal implements Handler.Callback, +/* package */ final class ExoPlayerImplInternal implements Handler.Callback, MediaPeriod.Callback, TrackSelector.InvalidationListener, MediaSource.Listener { /** @@ -64,15 +65,30 @@ import java.io.IOException; } + public static final class TrackInfo { + + public final TrackGroupArray groups; + public final TrackSelectionArray selections; + public final Object info; + + public TrackInfo(TrackGroupArray groups, TrackSelectionArray selections, Object info) { + this.groups = groups; + this.selections = selections; + this.info = info; + } + + } + private static final String TAG = "ExoPlayerImplInternal"; // External messages public static final int MSG_STATE_CHANGED = 1; public static final int MSG_LOADING_CHANGED = 2; - public static final int MSG_SEEK_ACK = 3; - public static final int MSG_POSITION_DISCONTINUITY = 4; - public static final int MSG_SOURCE_INFO_REFRESHED = 5; - public static final int MSG_ERROR = 6; + public static final int MSG_TRACKS_CHANGED = 3; + public static final int MSG_SEEK_ACK = 4; + public static final int MSG_POSITION_DISCONTINUITY = 5; + public static final int MSG_SOURCE_INFO_REFRESHED = 6; + public static final int MSG_ERROR = 7; // Internal messages private static final int MSG_PREPARE = 0; @@ -100,7 +116,7 @@ import java.io.IOException; private final Renderer[] renderers; private final RendererCapabilities[] rendererCapabilities; - private final TrackSelector trackSelector; + private final TrackSelector trackSelector; private final LoadControl loadControl; private final StandaloneMediaClock standaloneMediaClock; private final Handler handler; @@ -128,13 +144,13 @@ import java.io.IOException; private boolean isTimelineReady; private boolean isTimelineEnded; private int bufferAheadPeriodCount; - private MediaPeriodHolder playingPeriodHolder; - private MediaPeriodHolder readingPeriodHolder; - private MediaPeriodHolder loadingPeriodHolder; + private MediaPeriodHolder playingPeriodHolder; + private MediaPeriodHolder readingPeriodHolder; + private MediaPeriodHolder loadingPeriodHolder; private Timeline timeline; - public ExoPlayerImplInternal(Renderer[] renderers, TrackSelector trackSelector, + public ExoPlayerImplInternal(Renderer[] renderers, TrackSelector trackSelector, LoadControl loadControl, boolean playWhenReady, Handler eventHandler, PlaybackInfo playbackInfo) { this.renderers = renderers; @@ -538,7 +554,7 @@ import java.io.IOException; periodIndex = C.INDEX_UNSET; } - MediaPeriodHolder newPlayingPeriodHolder = null; + MediaPeriodHolder newPlayingPeriodHolder = null; if (playingPeriodHolder == null) { // We're still waiting for the first period to be prepared. if (loadingPeriodHolder != null) { @@ -546,7 +562,7 @@ import java.io.IOException; } } else { // Clear the timeline, but keep the requested period if it is already prepared. - MediaPeriodHolder periodHolder = playingPeriodHolder; + MediaPeriodHolder periodHolder = playingPeriodHolder; while (periodHolder != null) { if (periodHolder.index == periodIndex && periodHolder.prepared) { newPlayingPeriodHolder = periodHolder; @@ -680,7 +696,7 @@ import java.io.IOException; return; } // Reselect tracks on each period in turn, until the selection changes. - MediaPeriodHolder periodHolder = playingPeriodHolder; + MediaPeriodHolder periodHolder = playingPeriodHolder; boolean selectionsChangedForReadPeriod = true; while (true) { if (periodHolder == null || !periodHolder.prepared) { @@ -745,7 +761,7 @@ import java.io.IOException; } } } - trackSelector.onSelectionActivated(playingPeriodHolder.trackSelections); + eventHandler.obtainMessage(MSG_TRACKS_CHANGED, periodHolder.getTrackInfo()).sendToTarget(); enableRenderers(rendererWasEnabledFlags, enabledRendererCount); } else { // Release and re-prepare/buffer periods after the one whose selection changed. @@ -817,11 +833,11 @@ import java.io.IOException; playingPeriodHolder.setIndex(timeline, timeline.getWindow(period.windowIndex, window), index); - MediaPeriodHolder previousPeriodHolder = playingPeriodHolder; + MediaPeriodHolder previousPeriodHolder = playingPeriodHolder; boolean seenReadingPeriod = false; bufferAheadPeriodCount = 0; while (previousPeriodHolder.next != null) { - MediaPeriodHolder periodHolder = previousPeriodHolder.next; + MediaPeriodHolder periodHolder = previousPeriodHolder.next; index++; timeline.getPeriod(index, period, true); if (!periodHolder.uid.equals(period.uid)) { @@ -962,9 +978,8 @@ import java.io.IOException; MediaPeriod newMediaPeriod = mediaSource.createPeriod(newLoadingPeriodIndex, loadControl.getAllocator(), periodStartPositionUs); newMediaPeriod.prepare(this); - MediaPeriodHolder newPeriodHolder = new MediaPeriodHolder<>(renderers, - rendererCapabilities, trackSelector, mediaSource, newMediaPeriod, newPeriodUid, - periodStartPositionUs); + MediaPeriodHolder newPeriodHolder = new MediaPeriodHolder(renderers, rendererCapabilities, + trackSelector, mediaSource, newMediaPeriod, newPeriodUid, periodStartPositionUs); timeline.getWindow(windowIndex, window); newPeriodHolder.setIndex(timeline, window, newLoadingPeriodIndex); if (loadingPeriodHolder != null) { @@ -1018,9 +1033,9 @@ import java.io.IOException; } } if (readingPeriodHolder.next != null && readingPeriodHolder.next.prepared) { - TrackSelections oldTrackSelections = readingPeriodHolder.trackSelections; + TrackSelectionArray oldTrackSelections = readingPeriodHolder.trackSelections; readingPeriodHolder = readingPeriodHolder.next; - TrackSelections newTrackSelections = readingPeriodHolder.trackSelections; + TrackSelectionArray newTrackSelections = readingPeriodHolder.trackSelections; for (int i = 0; i < renderers.length; i++) { Renderer renderer = renderers[i]; TrackSelection oldSelection = oldTrackSelections.get(i); @@ -1094,14 +1109,14 @@ import java.io.IOException; } } - private void releasePeriodHoldersFrom(MediaPeriodHolder periodHolder) { + private void releasePeriodHoldersFrom(MediaPeriodHolder periodHolder) { while (periodHolder != null) { periodHolder.release(); periodHolder = periodHolder.next; } } - private void setPlayingPeriodHolder(MediaPeriodHolder periodHolder) + private void setPlayingPeriodHolder(MediaPeriodHolder periodHolder) throws ExoPlaybackException { int enabledRendererCount = 0; boolean[] rendererWasEnabledFlags = new boolean[renderers.length]; @@ -1125,7 +1140,7 @@ import java.io.IOException; } } - trackSelector.onSelectionActivated(periodHolder.trackSelections); + eventHandler.obtainMessage(MSG_TRACKS_CHANGED, periodHolder.getTrackInfo()).sendToTarget(); playingPeriodHolder = periodHolder; enableRenderers(rendererWasEnabledFlags, enabledRendererCount); } @@ -1182,7 +1197,7 @@ import java.io.IOException; /** * Holds a {@link MediaPeriod} with information required to play it as part of a timeline. */ - private static final class MediaPeriodHolder { + private static final class MediaPeriodHolder { public final MediaPeriod mediaPeriod; public final Object uid; @@ -1196,19 +1211,21 @@ import java.io.IOException; public boolean prepared; public boolean hasEnabledTracks; public long rendererPositionOffsetUs; - public MediaPeriodHolder next; + public MediaPeriodHolder next; public boolean needsContinueLoading; private final Renderer[] renderers; private final RendererCapabilities[] rendererCapabilities; - private final TrackSelector trackSelector; + private final TrackSelector trackSelector; private final MediaSource mediaSource; - private TrackSelections trackSelections; - private TrackSelections periodTrackSelections; + private Object trackSelectionsInfo; + private TrackGroupArray trackGroups; + private TrackSelectionArray trackSelections; + private TrackSelectionArray periodTrackSelections; public MediaPeriodHolder(Renderer[] renderers, RendererCapabilities[] rendererCapabilities, - TrackSelector trackSelector, MediaSource mediaSource, MediaPeriod mediaPeriod, + TrackSelector trackSelector, MediaSource mediaSource, MediaPeriod mediaPeriod, Object uid, long positionUs) { this.renderers = renderers; this.rendererCapabilities = rendererCapabilities; @@ -1221,7 +1238,7 @@ import java.io.IOException; startPositionUs = positionUs; } - public void setNext(MediaPeriodHolder next) { + public void setNext(MediaPeriodHolder next) { this.next = next; } @@ -1238,17 +1255,20 @@ import java.io.IOException; public void handlePrepared(long positionUs, LoadControl loadControl) throws ExoPlaybackException { prepared = true; + trackGroups = mediaPeriod.getTrackGroups(); selectTracks(); startPositionUs = updatePeriodTrackSelection(positionUs, loadControl, false); } public boolean selectTracks() throws ExoPlaybackException { - TrackSelections newTrackSelections = trackSelector.selectTracks(rendererCapabilities, - mediaPeriod.getTrackGroups()); + Pair selectorResult = trackSelector.selectTracks( + rendererCapabilities, trackGroups); + TrackSelectionArray newTrackSelections = selectorResult.first; if (newTrackSelections.equals(periodTrackSelections)) { return false; } trackSelections = newTrackSelections; + trackSelectionsInfo = selectorResult.second; return true; } @@ -1283,10 +1303,14 @@ import java.io.IOException; } // The track selection has changed. - loadControl.onTracksSelected(renderers, mediaPeriod.getTrackGroups(), trackSelections); + loadControl.onTracksSelected(renderers, trackGroups, trackSelections); return positionUs; } + public TrackInfo getTrackInfo() { + return new TrackInfo(trackGroups, trackSelections, trackSelectionsInfo); + } + public void release() { try { mediaSource.releasePeriod(mediaPeriod); diff --git a/library/src/main/java/com/google/android/exoplayer2/LoadControl.java b/library/src/main/java/com/google/android/exoplayer2/LoadControl.java index 6176c6085b..c092480222 100644 --- a/library/src/main/java/com/google/android/exoplayer2/LoadControl.java +++ b/library/src/main/java/com/google/android/exoplayer2/LoadControl.java @@ -17,7 +17,7 @@ package com.google.android.exoplayer2; import com.google.android.exoplayer2.source.TrackGroup; import com.google.android.exoplayer2.source.TrackGroupArray; -import com.google.android.exoplayer2.trackselection.TrackSelections; +import com.google.android.exoplayer2.trackselection.TrackSelectionArray; import com.google.android.exoplayer2.upstream.Allocator; /** @@ -38,7 +38,7 @@ public interface LoadControl { * @param trackSelections The track selections that were made. */ void onTracksSelected(Renderer[] renderers, TrackGroupArray trackGroups, - TrackSelections trackSelections); + TrackSelectionArray trackSelections); /** * Called by the player when stopped. diff --git a/library/src/main/java/com/google/android/exoplayer2/SimpleExoPlayer.java b/library/src/main/java/com/google/android/exoplayer2/SimpleExoPlayer.java index f41706091d..ad12002b17 100644 --- a/library/src/main/java/com/google/android/exoplayer2/SimpleExoPlayer.java +++ b/library/src/main/java/com/google/android/exoplayer2/SimpleExoPlayer.java @@ -39,9 +39,10 @@ import com.google.android.exoplayer2.metadata.Metadata; import com.google.android.exoplayer2.metadata.MetadataRenderer; import com.google.android.exoplayer2.metadata.id3.Id3Decoder; import com.google.android.exoplayer2.source.MediaSource; +import com.google.android.exoplayer2.source.TrackGroupArray; import com.google.android.exoplayer2.text.Cue; import com.google.android.exoplayer2.text.TextRenderer; -import com.google.android.exoplayer2.trackselection.TrackSelections; +import com.google.android.exoplayer2.trackselection.TrackSelectionArray; import com.google.android.exoplayer2.trackselection.TrackSelector; import com.google.android.exoplayer2.video.MediaCodecVideoRenderer; import com.google.android.exoplayer2.video.VideoRendererEventListener; @@ -86,11 +87,6 @@ public final class SimpleExoPlayer implements ExoPlayer { */ void onRenderedFirstFrame(); - /** - * Called when a video track is no longer selected. - */ - void onVideoTracksDisabled(); - } private static final String TAG = "SimpleExoPlayer"; @@ -103,7 +99,6 @@ public final class SimpleExoPlayer implements ExoPlayer { private final int videoRendererCount; private final int audioRendererCount; - private boolean videoTracksEnabled; private Format videoFormat; private Format audioFormat; @@ -122,12 +117,11 @@ public final class SimpleExoPlayer implements ExoPlayer { private float volume; private PlaybackParamsHolder playbackParamsHolder; - /* package */ SimpleExoPlayer(Context context, TrackSelector trackSelector, + /* package */ SimpleExoPlayer(Context context, TrackSelector trackSelector, LoadControl loadControl, DrmSessionManager drmSessionManager, boolean preferExtensionDecoders, long allowedVideoJoiningTimeMs) { mainHandler = new Handler(); componentListener = new ComponentListener(); - trackSelector.addListener(componentListener); // Build the renderers. ArrayList renderersList = new ArrayList<>(); @@ -164,26 +158,6 @@ public final class SimpleExoPlayer implements ExoPlayer { player = new ExoPlayerImpl(renderers, trackSelector, loadControl); } - /** - * Returns the number of renderers. - * - * @return The number of renderers. - */ - public int getRendererCount() { - return renderers.length; - } - - /** - * Returns the track type that the renderer at a given index handles. - * - * @see Renderer#getTrackType() - * @param index The index of the renderer. - * @return One of the {@code TRACK_TYPE_*} constants defined in {@link C}. - */ - public int getRendererType(int index) { - return renderers[index].getTrackType(); - } - /** * Clears any {@link Surface}, {@link SurfaceHolder}, {@link SurfaceView} or {@link TextureView} * currently set on the player. @@ -526,6 +500,26 @@ public final class SimpleExoPlayer implements ExoPlayer { return player.getBufferedPercentage(); } + @Override + public int getRendererCount() { + return player.getRendererCount(); + } + + @Override + public int getRendererType(int index) { + return player.getRendererType(index); + } + + @Override + public TrackGroupArray getCurrentTrackGroups() { + return player.getCurrentTrackGroups(); + } + + @Override + public TrackSelectionArray getCurrentTrackSelections() { + return player.getCurrentTrackSelections(); + } + @Override public Timeline getCurrentTimeline() { return player.getCurrentTimeline(); @@ -660,8 +654,7 @@ public final class SimpleExoPlayer implements ExoPlayer { private final class ComponentListener implements VideoRendererEventListener, AudioRendererEventListener, TextRenderer.Output, MetadataRenderer.Output, - SurfaceHolder.Callback, TextureView.SurfaceTextureListener, - TrackSelector.EventListener { + SurfaceHolder.Callback, TextureView.SurfaceTextureListener { // VideoRendererEventListener implementation @@ -840,23 +833,6 @@ public final class SimpleExoPlayer implements ExoPlayer { // Do nothing. } - // TrackSelector.EventListener implementation - - @Override - public void onTrackSelectionsChanged(TrackSelections trackSelections) { - boolean videoTracksEnabled = false; - for (int i = 0; i < renderers.length; i++) { - if (renderers[i].getTrackType() == C.TRACK_TYPE_VIDEO && trackSelections.get(i) != null) { - videoTracksEnabled = true; - break; - } - } - if (videoListener != null && SimpleExoPlayer.this.videoTracksEnabled && !videoTracksEnabled) { - videoListener.onVideoTracksDisabled(); - } - SimpleExoPlayer.this.videoTracksEnabled = videoTracksEnabled; - } - } @TargetApi(23) diff --git a/library/src/main/java/com/google/android/exoplayer2/extractor/ts/DefaultTsPayloadReaderFactory.java b/library/src/main/java/com/google/android/exoplayer2/extractor/ts/DefaultTsPayloadReaderFactory.java index 5aabc29a5d..96f7ff423f 100644 --- a/library/src/main/java/com/google/android/exoplayer2/extractor/ts/DefaultTsPayloadReaderFactory.java +++ b/library/src/main/java/com/google/android/exoplayer2/extractor/ts/DefaultTsPayloadReaderFactory.java @@ -16,6 +16,7 @@ package com.google.android.exoplayer2.extractor.ts; import android.support.annotation.IntDef; +import android.util.SparseArray; import com.google.android.exoplayer2.extractor.ts.TsPayloadReader.EsInfo; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -49,6 +50,11 @@ public final class DefaultTsPayloadReaderFactory implements TsPayloadReader.Fact this.flags = flags; } + @Override + public SparseArray createInitialPayloadReaders() { + return new SparseArray<>(); + } + @Override public TsPayloadReader createPayloadReader(int streamType, EsInfo esInfo) { switch (streamType) { diff --git a/library/src/main/java/com/google/android/exoplayer2/extractor/ts/SectionPayloadReader.java b/library/src/main/java/com/google/android/exoplayer2/extractor/ts/SectionPayloadReader.java index 9be41af594..2bddc56582 100644 --- a/library/src/main/java/com/google/android/exoplayer2/extractor/ts/SectionPayloadReader.java +++ b/library/src/main/java/com/google/android/exoplayer2/extractor/ts/SectionPayloadReader.java @@ -15,6 +15,10 @@ */ package com.google.android.exoplayer2.extractor.ts; +import com.google.android.exoplayer2.extractor.ExtractorOutput; +import com.google.android.exoplayer2.extractor.TimestampAdjuster; +import com.google.android.exoplayer2.extractor.TrackOutput; +import com.google.android.exoplayer2.extractor.ts.TsPayloadReader.TrackIdGenerator; import com.google.android.exoplayer2.util.ParsableByteArray; /** @@ -22,6 +26,17 @@ import com.google.android.exoplayer2.util.ParsableByteArray; */ public interface SectionPayloadReader { + /** + * Initializes the section payload reader. + * + * @param timestampAdjuster A timestamp adjuster for offsetting and scaling sample timestamps. + * @param extractorOutput The {@link ExtractorOutput} that receives the extracted data. + * @param idGenerator A {@link PesReader.TrackIdGenerator} that generates unique track ids for the + * {@link TrackOutput}s. + */ + void init(TimestampAdjuster timestampAdjuster, ExtractorOutput extractorOutput, + TrackIdGenerator idGenerator); + /** * Called by a {@link SectionReader} when a full section is received. * diff --git a/library/src/main/java/com/google/android/exoplayer2/extractor/ts/SectionReader.java b/library/src/main/java/com/google/android/exoplayer2/extractor/ts/SectionReader.java index ccf00f8d19..9a181897ab 100644 --- a/library/src/main/java/com/google/android/exoplayer2/extractor/ts/SectionReader.java +++ b/library/src/main/java/com/google/android/exoplayer2/extractor/ts/SectionReader.java @@ -43,8 +43,7 @@ public final class SectionReader implements TsPayloadReader { @Override public void init(TimestampAdjuster timestampAdjuster, ExtractorOutput extractorOutput, TrackIdGenerator idGenerator) { - // TODO: Injectable section readers might want to generate metadata tracks. - // Do nothing. + reader.init(timestampAdjuster, extractorOutput, idGenerator); } @Override diff --git a/library/src/main/java/com/google/android/exoplayer2/extractor/ts/TsExtractor.java b/library/src/main/java/com/google/android/exoplayer2/extractor/ts/TsExtractor.java index b52b5cc047..219101b8d3 100644 --- a/library/src/main/java/com/google/android/exoplayer2/extractor/ts/TsExtractor.java +++ b/library/src/main/java/com/google/android/exoplayer2/extractor/ts/TsExtractor.java @@ -65,8 +65,6 @@ public final class TsExtractor implements Extractor { public static final int TS_STREAM_TYPE_H265 = 0x24; public static final int TS_STREAM_TYPE_ID3 = 0x15; - private static final String TAG = "TsExtractor"; - private static final int TS_PACKET_SIZE = 188; private static final int TS_SYNC_BYTE = 0x47; // First byte of each TS packet. private static final int TS_PAT_PID = 0; @@ -253,6 +251,12 @@ public final class TsExtractor implements Extractor { private void resetPayloadReaders() { trackIds.clear(); tsPayloadReaders.clear(); + SparseArray initialPayloadReaders = + payloadReaderFactory.createInitialPayloadReaders(); + int initialPayloadReadersSize = initialPayloadReaders.size(); + for (int i = 0; i < initialPayloadReadersSize; i++) { + tsPayloadReaders.put(initialPayloadReaders.keyAt(i), initialPayloadReaders.valueAt(i)); + } tsPayloadReaders.put(TS_PAT_PID, new SectionReader(new PatReader())); id3Reader = null; } @@ -268,6 +272,12 @@ public final class TsExtractor implements Extractor { patScratch = new ParsableBitArray(new byte[4]); } + @Override + public void init(TimestampAdjuster timestampAdjuster, ExtractorOutput extractorOutput, + TrackIdGenerator idGenerator) { + // Do nothing. + } + @Override public void consume(ParsableByteArray sectionData) { // table_id(8), section_syntax_indicator(1), '0'(1), reserved(2), section_length(12), @@ -310,6 +320,12 @@ public final class TsExtractor implements Extractor { this.pid = pid; } + @Override + public void init(TimestampAdjuster timestampAdjuster, ExtractorOutput extractorOutput, + TrackIdGenerator idGenerator) { + // Do nothing. + } + @Override public void consume(ParsableByteArray sectionData) { // table_id(8), section_syntax_indicator(1), '0'(1), reserved(2), section_length(12), diff --git a/library/src/main/java/com/google/android/exoplayer2/extractor/ts/TsPayloadReader.java b/library/src/main/java/com/google/android/exoplayer2/extractor/ts/TsPayloadReader.java index 28e9fb9095..304c8c1282 100644 --- a/library/src/main/java/com/google/android/exoplayer2/extractor/ts/TsPayloadReader.java +++ b/library/src/main/java/com/google/android/exoplayer2/extractor/ts/TsPayloadReader.java @@ -15,6 +15,7 @@ */ package com.google.android.exoplayer2.extractor.ts; +import android.util.SparseArray; import com.google.android.exoplayer2.extractor.ExtractorOutput; import com.google.android.exoplayer2.extractor.TimestampAdjuster; import com.google.android.exoplayer2.extractor.TrackOutput; @@ -30,6 +31,15 @@ public interface TsPayloadReader { */ interface Factory { + /** + * Returns the initial mapping from PIDs to payload readers. + *

+ * This method allows the injection of payload readers for reserved PIDs, excluding PID 0. + * + * @return A {@link SparseArray} that maps PIDs to payload readers. + */ + SparseArray createInitialPayloadReaders(); + /** * Returns a {@link TsPayloadReader} for a given stream type and elementary stream information. * May return null if the stream type is not supported. @@ -89,9 +99,10 @@ public interface TsPayloadReader { /** * Initializes the payload reader. * - * @param timestampAdjuster - * @param extractorOutput - * @param idGenerator + * @param timestampAdjuster A timestamp adjuster for offsetting and scaling sample timestamps. + * @param extractorOutput The {@link ExtractorOutput} that receives the extracted data. + * @param idGenerator A {@link PesReader.TrackIdGenerator} that generates unique track ids for the + * {@link TrackOutput}s. */ void init(TimestampAdjuster timestampAdjuster, ExtractorOutput extractorOutput, TrackIdGenerator idGenerator); diff --git a/library/src/main/java/com/google/android/exoplayer2/source/TrackGroupArray.java b/library/src/main/java/com/google/android/exoplayer2/source/TrackGroupArray.java index d562ec43e1..394cec891b 100644 --- a/library/src/main/java/com/google/android/exoplayer2/source/TrackGroupArray.java +++ b/library/src/main/java/com/google/android/exoplayer2/source/TrackGroupArray.java @@ -23,6 +23,11 @@ import java.util.Arrays; */ public final class TrackGroupArray { + /** + * The empty array. + */ + public static final TrackGroupArray EMPTY = new TrackGroupArray(); + /** * The number of groups in the array. Greater than or equal to zero. */ diff --git a/library/src/main/java/com/google/android/exoplayer2/trackselection/DefaultTrackSelector.java b/library/src/main/java/com/google/android/exoplayer2/trackselection/DefaultTrackSelector.java index 81d79ac055..02c2defdfc 100644 --- a/library/src/main/java/com/google/android/exoplayer2/trackselection/DefaultTrackSelector.java +++ b/library/src/main/java/com/google/android/exoplayer2/trackselection/DefaultTrackSelector.java @@ -17,7 +17,6 @@ package com.google.android.exoplayer2.trackselection; import android.content.Context; import android.graphics.Point; -import android.os.Handler; import android.text.TextUtils; import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.ExoPlaybackException; @@ -326,25 +325,18 @@ public class DefaultTrackSelector extends MappingTrackSelector { /** * Constructs an instance that does not support adaptive video. - * - * @param eventHandler A handler to use when delivering events to listeners. May be null if - * listeners will not be added. */ - public DefaultTrackSelector(Handler eventHandler) { - this(eventHandler, null); + public DefaultTrackSelector() { + this(null); } /** * Constructs an instance that uses a factory to create adaptive video track selections. * - * @param eventHandler A handler to use when delivering events to listeners. May be null if - * listeners will not be added. * @param adaptiveVideoTrackSelectionFactory A factory for adaptive video {@link TrackSelection}s, * or null if the selector should not support adaptive video. */ - public DefaultTrackSelector(Handler eventHandler, - TrackSelection.Factory adaptiveVideoTrackSelectionFactory) { - super(eventHandler); + public DefaultTrackSelector(TrackSelection.Factory adaptiveVideoTrackSelectionFactory) { this.adaptiveVideoTrackSelectionFactory = adaptiveVideoTrackSelectionFactory; params = new AtomicReference<>(new Parameters()); } diff --git a/library/src/main/java/com/google/android/exoplayer2/trackselection/MappingTrackSelector.java b/library/src/main/java/com/google/android/exoplayer2/trackselection/MappingTrackSelector.java index 3307fc3baa..7454ff6801 100644 --- a/library/src/main/java/com/google/android/exoplayer2/trackselection/MappingTrackSelector.java +++ b/library/src/main/java/com/google/android/exoplayer2/trackselection/MappingTrackSelector.java @@ -15,14 +15,13 @@ */ package com.google.android.exoplayer2.trackselection; -import android.os.Handler; +import android.util.Pair; import android.util.SparseArray; import android.util.SparseBooleanArray; import com.google.android.exoplayer2.ExoPlaybackException; import com.google.android.exoplayer2.RendererCapabilities; import com.google.android.exoplayer2.source.TrackGroup; import com.google.android.exoplayer2.source.TrackGroupArray; -import com.google.android.exoplayer2.trackselection.MappingTrackSelector.MappedTrackInfo; import com.google.android.exoplayer2.util.Util; import java.util.Arrays; import java.util.HashMap; @@ -32,7 +31,7 @@ import java.util.Map; * Base class for {@link TrackSelector}s that first establish a mapping between {@link TrackGroup}s * and renderers, and then from that mapping create a {@link TrackSelection} for each renderer. */ -public abstract class MappingTrackSelector extends TrackSelector { +public abstract class MappingTrackSelector extends TrackSelector { /** * A track selection override. @@ -83,16 +82,21 @@ public abstract class MappingTrackSelector extends TrackSelector> selectionOverrides; private final SparseBooleanArray rendererDisabledFlags; - /** - * @param eventHandler A handler to use when delivering events to listeners added via - * {@link #addListener(EventListener)}. - */ - public MappingTrackSelector(Handler eventHandler) { - super(eventHandler); + private MappedTrackInfo currentMappedTrackInfo; + + public MappingTrackSelector() { selectionOverrides = new SparseArray<>(); rendererDisabledFlags = new SparseBooleanArray(); } + /** + * Returns the mapping information associated with the current track selections, or null if no + * selection is currently active. + */ + public final MappedTrackInfo getCurrentMappedTrackInfo() { + return currentMappedTrackInfo; + } + /** * Sets whether the renderer at the specified index is disabled. * @@ -224,7 +228,7 @@ public abstract class MappingTrackSelector extends TrackSelector selectTracks( + public final Pair selectTracks( RendererCapabilities[] rendererCapabilities, TrackGroupArray trackGroups) throws ExoPlaybackException { // Structures into which data will be written during the selection. The extra item at the end @@ -294,7 +298,13 @@ public abstract class MappingTrackSelector extends TrackSelector(mappedTrackInfo, trackSelections); + return Pair.create(new TrackSelectionArray(trackSelections), + mappedTrackInfo); + } + + @Override + public final void onSelectionActivated(Object info) { + currentMappedTrackInfo = (MappedTrackInfo) info; } /** @@ -409,12 +419,16 @@ public abstract class MappingTrackSelector extends TrackSelector { +public final class TrackSelectionArray { - /** - * Opaque information associated with the result. - */ - public final T info; /** * The number of selections in the result. Greater than or equal to zero. */ @@ -37,11 +33,9 @@ public final class TrackSelections { private int hashCode; /** - * @param info Opaque information associated with the result. * @param trackSelections The selections. Must not be null, but may contain null elements. */ - public TrackSelections(T info, TrackSelection... trackSelections) { - this.info = info; + public TrackSelectionArray(TrackSelection... trackSelections) { this.trackSelections = trackSelections; this.length = trackSelections.length; } @@ -81,7 +75,7 @@ public final class TrackSelections { if (obj == null || getClass() != obj.getClass()) { return false; } - TrackSelections other = (TrackSelections) obj; + TrackSelectionArray other = (TrackSelectionArray) obj; return Arrays.equals(trackSelections, other.trackSelections); } diff --git a/library/src/main/java/com/google/android/exoplayer2/trackselection/TrackSelector.java b/library/src/main/java/com/google/android/exoplayer2/trackselection/TrackSelector.java index 41c62f6e0e..5a9d3923bf 100644 --- a/library/src/main/java/com/google/android/exoplayer2/trackselection/TrackSelector.java +++ b/library/src/main/java/com/google/android/exoplayer2/trackselection/TrackSelector.java @@ -15,15 +15,13 @@ */ package com.google.android.exoplayer2.trackselection; -import android.os.Handler; +import android.util.Pair; import com.google.android.exoplayer2.ExoPlaybackException; import com.google.android.exoplayer2.RendererCapabilities; import com.google.android.exoplayer2.source.TrackGroupArray; -import com.google.android.exoplayer2.util.Assertions; -import java.util.concurrent.CopyOnWriteArraySet; /** Selects tracks to be consumed by available renderers. */ -public abstract class TrackSelector { +public abstract class TrackSelector { /** * Notified when previous selections by a {@link TrackSelector} are no longer valid. @@ -37,55 +35,7 @@ public abstract class TrackSelector { } - /** Listener of {@link TrackSelector} events. */ - public interface EventListener { - - /** - * Called when the track selections have changed. - * - * @param trackSelections The new track selections. - */ - void onTrackSelectionsChanged(TrackSelections trackSelections); - } - - private final Handler eventHandler; - private final CopyOnWriteArraySet> listeners; - private InvalidationListener listener; - private TrackSelections activeSelections; - - /** - * @param eventHandler A handler to use when delivering events to listeners added via {@link - * #addListener(EventListener)}. - */ - public TrackSelector(Handler eventHandler) { - this.eventHandler = Assertions.checkNotNull(eventHandler); - this.listeners = new CopyOnWriteArraySet<>(); - } - - /** - * Registers a listener to receive events from the selector. The listener's methods will be called - * using the {@link Handler} that was passed to the constructor. - * - * @param listener The listener to register. - */ - public final void addListener(EventListener listener) { - listeners.add(listener); - } - - /** - * Unregister a listener. The listener will no longer receive events from the selector. - * - * @param listener The listener to unregister. - */ - public final void removeListener(EventListener listener) { - listeners.remove(listener); - } - - /** Returns the current track selections. */ - public final TrackSelections getCurrentSelections() { - return activeSelections; - } /** * Initializes the selector. @@ -97,28 +47,27 @@ public abstract class TrackSelector { } /** - * Generates {@link TrackSelections} for the renderers. + * Generates {@link TrackSelectionArray} for the renderers. * - * @param rendererCapabilities The {@link RendererCapabilities} of the renderers for which {@link - * TrackSelection}s are to be generated. + * @param rendererCapabilities The {@link RendererCapabilities} of the renderers for which + * {@link TrackSelection}s are to be generated. * @param trackGroups The available track groups. - * @return The track selections. + * @return The track selections, and an implementation specific object that will be returned to + * the selector via {@link #onSelectionActivated(Object)} should the selections be activated. * @throws ExoPlaybackException If an error occurs selecting tracks. */ - public abstract TrackSelections selectTracks( + public abstract Pair selectTracks( RendererCapabilities[] rendererCapabilities, TrackGroupArray trackGroups) throws ExoPlaybackException; /** - * Called when {@link TrackSelections} previously generated by {@link - * #selectTracks(RendererCapabilities[], TrackGroupArray)} are activated. + * Called when {@link TrackSelectionArray} previously generated by + * {@link #selectTracks(RendererCapabilities[], TrackGroupArray)} are activated. * - * @param activeSelections The activated {@link TrackSelections}. + * @param info The information associated with the selections, or null if the selected tracks are + * being cleared. */ - public final void onSelectionActivated(TrackSelections activeSelections) { - this.activeSelections = activeSelections; - notifyTrackSelectionsChanged(activeSelections); - } + public abstract void onSelectionActivated(Object info); /** * Invalidates all previously generated track selections. @@ -129,18 +78,4 @@ public abstract class TrackSelector { } } - private void notifyTrackSelectionsChanged(final TrackSelections activeSelections) { - if (eventHandler != null) { - eventHandler.post( - new Runnable() { - @Override - public void run() { - for (EventListener listener : listeners) { - listener.onTrackSelectionsChanged(activeSelections); - } - } - }); - } - } - } diff --git a/library/src/main/java/com/google/android/exoplayer2/ui/DebugTextViewHelper.java b/library/src/main/java/com/google/android/exoplayer2/ui/DebugTextViewHelper.java index af38836fc9..1bf5b59a4a 100644 --- a/library/src/main/java/com/google/android/exoplayer2/ui/DebugTextViewHelper.java +++ b/library/src/main/java/com/google/android/exoplayer2/ui/DebugTextViewHelper.java @@ -22,6 +22,8 @@ import com.google.android.exoplayer2.Format; import com.google.android.exoplayer2.SimpleExoPlayer; import com.google.android.exoplayer2.Timeline; import com.google.android.exoplayer2.decoder.DecoderCounters; +import com.google.android.exoplayer2.source.TrackGroupArray; +import com.google.android.exoplayer2.trackselection.TrackSelectionArray; /** * A helper class for periodically updating a {@link TextView} with debug information obtained from @@ -98,6 +100,11 @@ public final class DebugTextViewHelper implements Runnable, ExoPlayer.EventListe // Do nothing. } + @Override + public void onTracksChanged(TrackGroupArray tracks, TrackSelectionArray selections) { + // Do nothing. + } + // Runnable implementation. @Override diff --git a/library/src/main/java/com/google/android/exoplayer2/ui/PlaybackControlView.java b/library/src/main/java/com/google/android/exoplayer2/ui/PlaybackControlView.java index 89c778d072..49e1b6bafb 100644 --- a/library/src/main/java/com/google/android/exoplayer2/ui/PlaybackControlView.java +++ b/library/src/main/java/com/google/android/exoplayer2/ui/PlaybackControlView.java @@ -24,7 +24,6 @@ import android.view.KeyEvent; import android.view.LayoutInflater; import android.view.View; import android.widget.FrameLayout; -import android.widget.ImageButton; import android.widget.SeekBar; import android.widget.TextView; import com.google.android.exoplayer2.C; @@ -32,12 +31,24 @@ import com.google.android.exoplayer2.ExoPlaybackException; import com.google.android.exoplayer2.ExoPlayer; import com.google.android.exoplayer2.R; import com.google.android.exoplayer2.Timeline; +import com.google.android.exoplayer2.source.TrackGroupArray; +import com.google.android.exoplayer2.trackselection.TrackSelectionArray; import com.google.android.exoplayer2.util.Util; import java.util.Formatter; import java.util.Locale; /** * A view to control video playback of an {@link ExoPlayer}. + * + * By setting the view attribute {@code controller_layout_id} a layout resource to use can + * be customized. All views are optional but if the buttons should have an appropriate logic + * assigned, the id of the views in the layout have to match the expected ids as follows: + * + *

    + *
  • Playback: {@code exo_play}, {@code exo_pause}, {@code exo_ffwd}, {@code exo_rew}.
  • + *
  • Progress: {@code exo_progress}, {@code exo_time_current}, {@code exo_time}.
  • + *
  • Playlist navigation: {@code exo_previous}, {@code exo_next}.
  • + *
*/ public class PlaybackControlView extends FrameLayout { @@ -63,12 +74,13 @@ public class PlaybackControlView extends FrameLayout { private final ComponentListener componentListener; private final View previousButton; private final View nextButton; - private final ImageButton playButton; + private final View playButton; + private final View pauseButton; + private final View fastForwardButton; + private final View rewindButton; private final TextView time; private final TextView timeCurrent; private final SeekBar progressBar; - private final View fastForwardButton; - private final View rewindButton; private final StringBuilder formatBuilder; private final Formatter formatter; private final Timeline.Window currentWindow; @@ -108,6 +120,7 @@ public class PlaybackControlView extends FrameLayout { public PlaybackControlView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); + int layoutResourceId = R.layout.exo_playback_control_view; rewindMs = DEFAULT_REWIND_MS; fastForwardMs = DEFAULT_FAST_FORWARD_MS; showTimeoutMs = DEFAULT_SHOW_TIMEOUT_MS; @@ -119,32 +132,49 @@ public class PlaybackControlView extends FrameLayout { fastForwardMs = a.getInt(R.styleable.PlaybackControlView_fastforward_increment, fastForwardMs); showTimeoutMs = a.getInt(R.styleable.PlaybackControlView_show_timeout, showTimeoutMs); + layoutResourceId = a.getResourceId(R.styleable.PlaybackControlView_controller_layout_id, + layoutResourceId); } finally { a.recycle(); } } - currentWindow = new Timeline.Window(); formatBuilder = new StringBuilder(); formatter = new Formatter(formatBuilder, Locale.getDefault()); componentListener = new ComponentListener(); - LayoutInflater.from(context).inflate(R.layout.exo_playback_control_view, this); - time = (TextView) findViewById(R.id.time); - timeCurrent = (TextView) findViewById(R.id.time_current); - progressBar = (SeekBar) findViewById(R.id.mediacontroller_progress); - progressBar.setOnSeekBarChangeListener(componentListener); - progressBar.setMax(PROGRESS_BAR_MAX); - playButton = (ImageButton) findViewById(R.id.play); - playButton.setOnClickListener(componentListener); - previousButton = findViewById(R.id.prev); - previousButton.setOnClickListener(componentListener); - nextButton = findViewById(R.id.next); - nextButton.setOnClickListener(componentListener); - rewindButton = findViewById(R.id.rew); - rewindButton.setOnClickListener(componentListener); - fastForwardButton = findViewById(R.id.ffwd); - fastForwardButton.setOnClickListener(componentListener); + LayoutInflater.from(context).inflate(layoutResourceId, this); + time = (TextView) findViewById(R.id.exo_time); + timeCurrent = (TextView) findViewById(R.id.exo_time_current); + progressBar = (SeekBar) findViewById(R.id.exo_progress); + if (progressBar != null) { + progressBar.setOnSeekBarChangeListener(componentListener); + progressBar.setMax(PROGRESS_BAR_MAX); + } + playButton = findViewById(R.id.exo_play); + if (playButton != null) { + playButton.setOnClickListener(componentListener); + } + pauseButton = findViewById(R.id.exo_pause); + if (pauseButton != null) { + pauseButton.setOnClickListener(componentListener); + } + previousButton = findViewById(R.id.exo_prev); + if (previousButton != null) { + previousButton.setOnClickListener(componentListener); + } + nextButton = findViewById(R.id.exo_next); + if (nextButton != null) { + nextButton.setOnClickListener(componentListener); + } + rewindButton = findViewById(R.id.exo_rew); + if (rewindButton != null) { + rewindButton.setOnClickListener(componentListener); + } + fastForwardButton = findViewById(R.id.exo_ffwd); + if (fastForwardButton != null) { + fastForwardButton.setOnClickListener(componentListener); + } } /** @@ -285,11 +315,12 @@ public class PlaybackControlView extends FrameLayout { return; } boolean playing = player != null && player.getPlayWhenReady(); - String contentDescription = getResources().getString( - playing ? R.string.exo_controls_pause_description : R.string.exo_controls_play_description); - playButton.setContentDescription(contentDescription); - playButton.setImageResource( - playing ? R.drawable.exo_controls_pause : R.drawable.exo_controls_play); + if (playButton != null) { + playButton.setVisibility(playing ? GONE : VISIBLE); + } + if (pauseButton != null) { + pauseButton.setVisibility(playing ? VISIBLE : GONE); + } } private void updateNavigation() { @@ -313,7 +344,9 @@ public class PlaybackControlView extends FrameLayout { setButtonEnabled(enableNext, nextButton); setButtonEnabled(fastForwardMs > 0 && isSeekable, fastForwardButton); setButtonEnabled(rewindMs > 0 && isSeekable, rewindButton); - progressBar.setEnabled(isSeekable); + if (progressBar != null) { + progressBar.setEnabled(isSeekable); + } } private void updateProgress() { @@ -322,16 +355,21 @@ public class PlaybackControlView extends FrameLayout { } long duration = player == null ? 0 : player.getDuration(); long position = player == null ? 0 : player.getCurrentPosition(); - time.setText(stringForTime(duration)); - if (!dragging) { + if (time != null) { + time.setText(stringForTime(duration)); + } + if (timeCurrent != null && !dragging) { timeCurrent.setText(stringForTime(position)); } - if (!dragging) { - progressBar.setProgress(progressBarValue(position)); + + if (progressBar != null) { + if (!dragging) { + progressBar.setProgress(progressBarValue(position)); + } + long bufferedPosition = player == null ? 0 : player.getBufferedPosition(); + progressBar.setSecondaryProgress(progressBarValue(bufferedPosition)); + // Remove scheduled updates. } - long bufferedPosition = player == null ? 0 : player.getBufferedPosition(); - progressBar.setSecondaryProgress(progressBarValue(bufferedPosition)); - // Remove scheduled updates. removeCallbacks(updateProgressAction); // Schedule an update if necessary. int playbackState = player == null ? ExoPlayer.STATE_IDLE : player.getPlaybackState(); @@ -350,6 +388,9 @@ public class PlaybackControlView extends FrameLayout { } private void setButtonEnabled(boolean enabled, View view) { + if (view == null) { + return; + } view.setEnabled(enabled); if (Util.SDK_INT >= 11) { setViewAlphaV11(view, enabled ? 1f : 0.3f); @@ -500,7 +541,7 @@ public class PlaybackControlView extends FrameLayout { @Override public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { - if (fromUser) { + if (fromUser && timeCurrent != null) { timeCurrent.setText(stringForTime(positionValue(progress))); } } @@ -508,7 +549,9 @@ public class PlaybackControlView extends FrameLayout { @Override public void onStopTrackingTouch(SeekBar seekBar) { dragging = false; - player.seekTo(positionValue(seekBar.getProgress())); + if (player != null) { + player.seekTo(positionValue(seekBar.getProgress())); + } hideAfterTimeout(); } @@ -535,6 +578,11 @@ public class PlaybackControlView extends FrameLayout { // Do nothing. } + @Override + public void onTracksChanged(TrackGroupArray tracks, TrackSelectionArray selections) { + // Do nothing. + } + @Override public void onPlayerError(ExoPlaybackException error) { // Do nothing. @@ -542,17 +590,20 @@ public class PlaybackControlView extends FrameLayout { @Override public void onClick(View view) { - Timeline currentTimeline = player.getCurrentTimeline(); - if (nextButton == view) { - next(); - } else if (previousButton == view) { - previous(); - } else if (fastForwardButton == view) { - fastForward(); - } else if (rewindButton == view && currentTimeline != null) { - rewind(); - } else if (playButton == view) { - player.setPlayWhenReady(!player.getPlayWhenReady()); + if (player != null) { + if (nextButton == view) { + next(); + } else if (previousButton == view) { + previous(); + } else if (fastForwardButton == view) { + fastForward(); + } else if (rewindButton == view) { + rewind(); + } else if (playButton == view) { + player.setPlayWhenReady(true); + } else if (pauseButton == view) { + player.setPlayWhenReady(false); + } } hideAfterTimeout(); } diff --git a/library/src/main/java/com/google/android/exoplayer2/ui/SimpleExoPlayerView.java b/library/src/main/java/com/google/android/exoplayer2/ui/SimpleExoPlayerView.java index 51955ccef3..198e6870e8 100644 --- a/library/src/main/java/com/google/android/exoplayer2/ui/SimpleExoPlayerView.java +++ b/library/src/main/java/com/google/android/exoplayer2/ui/SimpleExoPlayerView.java @@ -27,13 +27,16 @@ import android.view.TextureView; import android.view.View; import android.view.ViewGroup; import android.widget.FrameLayout; +import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.ExoPlaybackException; import com.google.android.exoplayer2.ExoPlayer; import com.google.android.exoplayer2.R; import com.google.android.exoplayer2.SimpleExoPlayer; import com.google.android.exoplayer2.Timeline; +import com.google.android.exoplayer2.source.TrackGroupArray; import com.google.android.exoplayer2.text.Cue; import com.google.android.exoplayer2.text.TextRenderer; +import com.google.android.exoplayer2.trackselection.TrackSelectionArray; import java.util.List; /** @@ -90,23 +93,30 @@ public final class SimpleExoPlayerView extends FrameLayout { LayoutInflater.from(context).inflate(R.layout.exo_simple_player_view, this); componentListener = new ComponentListener(); - layout = (AspectRatioFrameLayout) findViewById(R.id.video_frame); + layout = (AspectRatioFrameLayout) findViewById(R.id.exo_video_frame); layout.setResizeMode(resizeMode); - shutterView = findViewById(R.id.shutter); - subtitleLayout = (SubtitleView) findViewById(R.id.subtitles); + shutterView = findViewById(R.id.exo_shutter); + subtitleLayout = (SubtitleView) findViewById(R.id.exo_subtitles); subtitleLayout.setUserDefaultStyle(); subtitleLayout.setUserDefaultTextSize(); - controller = (PlaybackControlView) findViewById(R.id.control); - controller.hide(); + View controllerPlaceholder = findViewById(R.id.exo_controller_placeholder); + + controller = new PlaybackControlView(context, attrs); controller.setRewindIncrementMs(rewindMs); controller.setFastForwardIncrementMs(fastForwardMs); + controller.setLayoutParams(controllerPlaceholder.getLayoutParams()); + controller.hide(); this.controllerShowTimeoutMs = controllerShowTimeoutMs; + ViewGroup parent = ((ViewGroup) controllerPlaceholder.getParent()); + int controllerIndex = parent.indexOfChild(controllerPlaceholder); + parent.removeView(controllerPlaceholder); + parent.addView(controller, controllerIndex); + View view = useTextureView ? new TextureView(context) : new SurfaceView(context); ViewGroup.LayoutParams params = new ViewGroup.LayoutParams( - ViewGroup.LayoutParams.MATCH_PARENT, - ViewGroup.LayoutParams.MATCH_PARENT); + ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT); view.setLayoutParams(params); surfaceView = view; layout.addView(surfaceView, 0); @@ -318,7 +328,13 @@ public final class SimpleExoPlayerView extends FrameLayout { } @Override - public void onVideoTracksDisabled() { + public void onTracksChanged(TrackGroupArray tracks, TrackSelectionArray selections) { + for (int i = 0; i < selections.length; i++) { + if (player.getRendererType(i) == C.TRACK_TYPE_VIDEO && selections.get(i) != null) { + return; + } + } + // No enabled video renderers. Close the shutter. shutterView.setVisibility(VISIBLE); } diff --git a/library/src/main/java/com/google/android/exoplayer2/ui/SubtitleView.java b/library/src/main/java/com/google/android/exoplayer2/ui/SubtitleView.java index 0c8d9ef92e..49516ab6f4 100644 --- a/library/src/main/java/com/google/android/exoplayer2/ui/SubtitleView.java +++ b/library/src/main/java/com/google/android/exoplayer2/ui/SubtitleView.java @@ -122,10 +122,10 @@ public final class SubtitleView extends View implements TextRenderer.Output { /** * Sets the text size to one derived from {@link CaptioningManager#getFontScale()}, or to a - * default size on API level 19 and earlier. + * default size before API level 19. */ public void setUserDefaultTextSize() { - float fontScale = Util.SDK_INT >= 19 ? getUserCaptionFontScaleV19() : 1f; + float fontScale = Util.SDK_INT >= 19 && !isInEditMode() ? getUserCaptionFontScaleV19() : 1f; setFractionalTextSize(DEFAULT_TEXT_SIZE_FRACTION * fontScale); } @@ -180,10 +180,11 @@ public final class SubtitleView extends View implements TextRenderer.Output { /** * Sets the caption style to be equivalent to the one returned by - * {@link CaptioningManager#getUserStyle()}, or to a default style on API level 19 and earlier. + * {@link CaptioningManager#getUserStyle()}, or to a default style before API level 19. */ public void setUserDefaultStyle() { - setStyle(Util.SDK_INT >= 19 ? getUserCaptionStyleV19() : CaptionStyleCompat.DEFAULT); + setStyle(Util.SDK_INT >= 19 && !isInEditMode() + ? getUserCaptionStyleV19() : CaptionStyleCompat.DEFAULT); } /** diff --git a/library/src/main/res/layout/exo_playback_control_view.xml b/library/src/main/res/layout/exo_playback_control_view.xml index 5c9d2aeeb9..f9de461b0c 100644 --- a/library/src/main/res/layout/exo_playback_control_view.xml +++ b/library/src/main/res/layout/exo_playback_control_view.xml @@ -14,7 +14,6 @@ limitations under the License. --> - - - + - + + - @@ -56,7 +53,7 @@ android:layout_height="wrap_content" android:orientation="horizontal"> - - - - - + - - diff --git a/library/src/main/res/values/attrs.xml b/library/src/main/res/values/attrs.xml index d58882c0aa..1cc22314c0 100644 --- a/library/src/main/res/values/attrs.xml +++ b/library/src/main/res/values/attrs.xml @@ -23,6 +23,7 @@ + @@ -31,6 +32,7 @@ + @@ -41,6 +43,7 @@ + diff --git a/library/src/main/res/values/ids.xml b/library/src/main/res/values/ids.xml new file mode 100644 index 0000000000..9e810f1c2b --- /dev/null +++ b/library/src/main/res/values/ids.xml @@ -0,0 +1,26 @@ + + + + + + + + + + + + + diff --git a/library/src/main/res/values/styles.xml b/library/src/main/res/values/styles.xml index fe1e26e967..3a1e73f887 100644 --- a/library/src/main/res/values/styles.xml +++ b/library/src/main/res/values/styles.xml @@ -41,4 +41,14 @@ @string/exo_controls_rewind_description + + + + diff --git a/playbacktests/src/main/java/com/google/android/exoplayer2/playbacktests/gts/DashTest.java b/playbacktests/src/main/java/com/google/android/exoplayer2/playbacktests/gts/DashTest.java index 9e9a03a277..8aee627993 100644 --- a/playbacktests/src/main/java/com/google/android/exoplayer2/playbacktests/gts/DashTest.java +++ b/playbacktests/src/main/java/com/google/android/exoplayer2/playbacktests/gts/DashTest.java @@ -19,8 +19,6 @@ import android.annotation.TargetApi; import android.media.MediaDrm; import android.media.UnsupportedSchemeException; import android.net.Uri; -import android.os.Handler; -import android.os.Looper; import android.test.ActivityInstrumentationTestCase2; import android.util.Log; import com.google.android.exoplayer2.C; @@ -805,7 +803,6 @@ public final class DashTest extends ActivityInstrumentationTestCase2