mirror of
https://github.com/androidx/media.git
synced 2025-05-17 12:39:52 +08:00
Merge branch 'dev-v2' into dev-v2-id3
This commit is contained in:
commit
0c8f3c7270
@ -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<MappedTrackInfo>, 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<? extends MappedTrackInfo> 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(
|
||||
|
@ -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<MappedTrackInfo>, 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<? extends MappedTrackInfo> 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<MappedTrackInfo> 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;
|
||||
|
@ -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.
|
||||
|
@ -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.
|
||||
|
@ -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.
|
||||
|
BIN
library/src/androidTest/assets/ts/sample_with_sdt.ts
Normal file
BIN
library/src/androidTest/assets/ts/sample_with_sdt.ts
Normal file
Binary file not shown.
@ -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<TsPayloadReader> createInitialPayloadReaders() {
|
||||
if (provideSdtReader) {
|
||||
assertNull(sdtReader);
|
||||
SparseArray<TsPayloadReader> 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++;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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) {
|
||||
|
@ -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}.
|
||||
|
@ -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<FrameworkMediaCrypto> 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<FrameworkMediaCrypto> 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<FrameworkMediaCrypto> 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);
|
||||
}
|
||||
|
@ -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<EventListener> 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;
|
||||
|
@ -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<T> 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<T> 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<T> playingPeriodHolder;
|
||||
private MediaPeriodHolder<T> readingPeriodHolder;
|
||||
private MediaPeriodHolder<T> loadingPeriodHolder;
|
||||
private MediaPeriodHolder playingPeriodHolder;
|
||||
private MediaPeriodHolder readingPeriodHolder;
|
||||
private MediaPeriodHolder loadingPeriodHolder;
|
||||
|
||||
private Timeline timeline;
|
||||
|
||||
public ExoPlayerImplInternal(Renderer[] renderers, TrackSelector<T> 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<T> 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<T> 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<T> 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<T> previousPeriodHolder = playingPeriodHolder;
|
||||
MediaPeriodHolder previousPeriodHolder = playingPeriodHolder;
|
||||
boolean seenReadingPeriod = false;
|
||||
bufferAheadPeriodCount = 0;
|
||||
while (previousPeriodHolder.next != null) {
|
||||
MediaPeriodHolder<T> 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<T> 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<T> oldTrackSelections = readingPeriodHolder.trackSelections;
|
||||
TrackSelectionArray oldTrackSelections = readingPeriodHolder.trackSelections;
|
||||
readingPeriodHolder = readingPeriodHolder.next;
|
||||
TrackSelections<T> 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<T> periodHolder) {
|
||||
private void releasePeriodHoldersFrom(MediaPeriodHolder periodHolder) {
|
||||
while (periodHolder != null) {
|
||||
periodHolder.release();
|
||||
periodHolder = periodHolder.next;
|
||||
}
|
||||
}
|
||||
|
||||
private void setPlayingPeriodHolder(MediaPeriodHolder<T> 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<T> {
|
||||
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<T> next;
|
||||
public MediaPeriodHolder next;
|
||||
public boolean needsContinueLoading;
|
||||
|
||||
private final Renderer[] renderers;
|
||||
private final RendererCapabilities[] rendererCapabilities;
|
||||
private final TrackSelector<T> trackSelector;
|
||||
private final TrackSelector trackSelector;
|
||||
private final MediaSource mediaSource;
|
||||
|
||||
private TrackSelections<T> trackSelections;
|
||||
private TrackSelections<T> periodTrackSelections;
|
||||
private Object trackSelectionsInfo;
|
||||
private TrackGroupArray trackGroups;
|
||||
private TrackSelectionArray trackSelections;
|
||||
private TrackSelectionArray periodTrackSelections;
|
||||
|
||||
public MediaPeriodHolder(Renderer[] renderers, RendererCapabilities[] rendererCapabilities,
|
||||
TrackSelector<T> 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<T> 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<T> newTrackSelections = trackSelector.selectTracks(rendererCapabilities,
|
||||
mediaPeriod.getTrackGroups());
|
||||
Pair<TrackSelectionArray, Object> 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);
|
||||
|
@ -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.
|
||||
|
@ -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<FrameworkMediaCrypto> drmSessionManager,
|
||||
boolean preferExtensionDecoders, long allowedVideoJoiningTimeMs) {
|
||||
mainHandler = new Handler();
|
||||
componentListener = new ComponentListener();
|
||||
trackSelector.addListener(componentListener);
|
||||
|
||||
// Build the renderers.
|
||||
ArrayList<Renderer> 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<Object> {
|
||||
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)
|
||||
|
@ -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<TsPayloadReader> createInitialPayloadReaders() {
|
||||
return new SparseArray<>();
|
||||
}
|
||||
|
||||
@Override
|
||||
public TsPayloadReader createPayloadReader(int streamType, EsInfo esInfo) {
|
||||
switch (streamType) {
|
||||
|
@ -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.
|
||||
*
|
||||
|
@ -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
|
||||
|
@ -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<TsPayloadReader> 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),
|
||||
|
@ -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.
|
||||
* <p>
|
||||
* 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<TsPayloadReader> 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);
|
||||
|
@ -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.
|
||||
*/
|
||||
|
@ -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());
|
||||
}
|
||||
|
@ -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<MappedTrackInfo> {
|
||||
public abstract class MappingTrackSelector extends TrackSelector {
|
||||
|
||||
/**
|
||||
* A track selection override.
|
||||
@ -83,16 +82,21 @@ public abstract class MappingTrackSelector extends TrackSelector<MappedTrackInfo
|
||||
private final SparseArray<Map<TrackGroupArray, SelectionOverride>> 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<MappedTrackInfo
|
||||
// TrackSelector implementation.
|
||||
|
||||
@Override
|
||||
public final TrackSelections<MappedTrackInfo> selectTracks(
|
||||
public final Pair<TrackSelectionArray, Object> 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
|
||||
MappedTrackInfo mappedTrackInfo = new MappedTrackInfo(rendererTrackTypes,
|
||||
rendererTrackGroupArrays, mixedMimeTypeAdaptationSupport, rendererFormatSupports,
|
||||
unassociatedTrackGroupArray);
|
||||
return new TrackSelections<>(mappedTrackInfo, trackSelections);
|
||||
return Pair.<TrackSelectionArray, Object>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<MappedTrackInfo
|
||||
*/
|
||||
public static final int RENDERER_SUPPORT_PLAYABLE_TRACKS = 2;
|
||||
|
||||
/**
|
||||
* The number of renderers to which tracks are mapped.
|
||||
*/
|
||||
public final int length;
|
||||
|
||||
private final int[] rendererTrackTypes;
|
||||
private final TrackGroupArray[] trackGroups;
|
||||
private final int[] mixedMimeTypeAdaptiveSupport;
|
||||
private final int[][][] formatSupport;
|
||||
private final TrackGroupArray unassociatedTrackGroups;
|
||||
private final int rendererCount;
|
||||
|
||||
/**
|
||||
* @param rendererTrackTypes The track type supported by each renderer.
|
||||
@ -433,7 +447,7 @@ public abstract class MappingTrackSelector extends TrackSelector<MappedTrackInfo
|
||||
this.formatSupport = formatSupport;
|
||||
this.mixedMimeTypeAdaptiveSupport = mixedMimeTypeAdaptiveSupport;
|
||||
this.unassociatedTrackGroups = unassociatedTrackGroups;
|
||||
this.rendererCount = trackGroups.length;
|
||||
this.length = trackGroups.length;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -573,7 +587,7 @@ public abstract class MappingTrackSelector extends TrackSelector<MappedTrackInfo
|
||||
*/
|
||||
public boolean hasOnlyUnplayableTracks(int trackType) {
|
||||
int rendererSupport = RENDERER_SUPPORT_NO_TRACKS;
|
||||
for (int i = 0; i < rendererCount; i++) {
|
||||
for (int i = 0; i < length; i++) {
|
||||
if (rendererTrackTypes[i] == trackType) {
|
||||
rendererSupport = Math.max(rendererSupport, getRendererSupport(i));
|
||||
}
|
||||
|
@ -20,12 +20,8 @@ import java.util.Arrays;
|
||||
/**
|
||||
* The result of a {@link TrackSelector} operation.
|
||||
*/
|
||||
public final class TrackSelections<T> {
|
||||
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<T> {
|
||||
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<T> {
|
||||
if (obj == null || getClass() != obj.getClass()) {
|
||||
return false;
|
||||
}
|
||||
TrackSelections<?> other = (TrackSelections<?>) obj;
|
||||
TrackSelectionArray other = (TrackSelectionArray) obj;
|
||||
return Arrays.equals(trackSelections, other.trackSelections);
|
||||
}
|
||||
|
@ -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<T> {
|
||||
public abstract class TrackSelector {
|
||||
|
||||
/**
|
||||
* Notified when previous selections by a {@link TrackSelector} are no longer valid.
|
||||
@ -37,55 +35,7 @@ public abstract class TrackSelector<T> {
|
||||
|
||||
}
|
||||
|
||||
/** Listener of {@link TrackSelector} events. */
|
||||
public interface EventListener<T> {
|
||||
|
||||
/**
|
||||
* Called when the track selections have changed.
|
||||
*
|
||||
* @param trackSelections The new track selections.
|
||||
*/
|
||||
void onTrackSelectionsChanged(TrackSelections<? extends T> trackSelections);
|
||||
}
|
||||
|
||||
private final Handler eventHandler;
|
||||
private final CopyOnWriteArraySet<MappingTrackSelector.EventListener<? super T>> listeners;
|
||||
|
||||
private InvalidationListener listener;
|
||||
private TrackSelections<T> 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<? super T> 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<? super T> listener) {
|
||||
listeners.remove(listener);
|
||||
}
|
||||
|
||||
/** Returns the current track selections. */
|
||||
public final TrackSelections<T> getCurrentSelections() {
|
||||
return activeSelections;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the selector.
|
||||
@ -97,28 +47,27 @@ public abstract class TrackSelector<T> {
|
||||
}
|
||||
|
||||
/**
|
||||
* 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<T> selectTracks(
|
||||
public abstract Pair<TrackSelectionArray, Object> 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<T> 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<T> {
|
||||
}
|
||||
}
|
||||
|
||||
private void notifyTrackSelectionsChanged(final TrackSelections<T> activeSelections) {
|
||||
if (eventHandler != null) {
|
||||
eventHandler.post(
|
||||
new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
for (EventListener<? super T> listener : listeners) {
|
||||
listener.onTrackSelectionsChanged(activeSelections);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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:
|
||||
*
|
||||
* <ul>
|
||||
* <li>Playback: {@code exo_play}, {@code exo_pause}, {@code exo_ffwd}, {@code exo_rew}.</li>
|
||||
* <li>Progress: {@code exo_progress}, {@code exo_time_current}, {@code exo_time}.</li>
|
||||
* <li>Playlist navigation: {@code exo_previous}, {@code exo_next}.</li>
|
||||
* </ul>
|
||||
*/
|
||||
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();
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -14,7 +14,6 @@
|
||||
limitations under the License.
|
||||
-->
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="bottom"
|
||||
@ -29,24 +28,22 @@
|
||||
android:paddingTop="4dp"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<ImageButton android:id="@+id/prev"
|
||||
android:contentDescription="@string/exo_controls_previous_description"
|
||||
<ImageButton android:id="@+id/exo_prev"
|
||||
style="@style/ExoMediaButton.Previous"/>
|
||||
|
||||
<ImageButton android:id="@+id/rew"
|
||||
android:contentDescription="@string/exo_controls_rewind_description"
|
||||
<ImageButton android:id="@+id/exo_rew"
|
||||
style="@style/ExoMediaButton.Rewind"/>
|
||||
|
||||
<ImageButton android:id="@+id/play"
|
||||
tools:ignore="ContentDescription"
|
||||
style="@style/ExoMediaButton"/>
|
||||
<ImageButton android:id="@+id/exo_play"
|
||||
style="@style/ExoMediaButton.Play"/>
|
||||
|
||||
<ImageButton android:id="@+id/ffwd"
|
||||
android:contentDescription="@string/exo_controls_fastforward_description"
|
||||
<ImageButton android:id="@+id/exo_pause"
|
||||
style="@style/ExoMediaButton.Pause"/>
|
||||
|
||||
<ImageButton android:id="@+id/exo_ffwd"
|
||||
style="@style/ExoMediaButton.FastForward"/>
|
||||
|
||||
<ImageButton android:id="@+id/next"
|
||||
android:contentDescription="@string/exo_controls_previous_description"
|
||||
<ImageButton android:id="@+id/exo_next"
|
||||
style="@style/ExoMediaButton.Next"/>
|
||||
|
||||
</LinearLayout>
|
||||
@ -56,7 +53,7 @@
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<TextView android:id="@+id/time_current"
|
||||
<TextView android:id="@+id/exo_time_current"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center_horizontal"
|
||||
@ -67,13 +64,13 @@
|
||||
android:paddingEnd="4dp"
|
||||
android:textColor="#FFBEBEBE"/>
|
||||
|
||||
<SeekBar android:id="@+id/mediacontroller_progress"
|
||||
<SeekBar android:id="@+id/exo_progress"
|
||||
android:layout_width="0dp"
|
||||
android:layout_weight="1"
|
||||
android:layout_height="32dp"
|
||||
style="?android:attr/progressBarStyleHorizontal"/>
|
||||
|
||||
<TextView android:id="@+id/time"
|
||||
<TextView android:id="@+id/exo_time"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center_horizontal"
|
||||
|
@ -17,23 +17,23 @@
|
||||
android:layout_height="match_parent"
|
||||
android:layout_width="match_parent">
|
||||
|
||||
<com.google.android.exoplayer2.ui.AspectRatioFrameLayout android:id="@+id/video_frame"
|
||||
<com.google.android.exoplayer2.ui.AspectRatioFrameLayout android:id="@+id/exo_video_frame"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_gravity="center">
|
||||
|
||||
<View android:id="@+id/shutter"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:background="@android:color/black"/>
|
||||
<View android:id="@+id/exo_shutter"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:background="@android:color/black"/>
|
||||
|
||||
<com.google.android.exoplayer2.ui.SubtitleView android:id="@+id/subtitles"
|
||||
<com.google.android.exoplayer2.ui.SubtitleView android:id="@+id/exo_subtitles"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"/>
|
||||
|
||||
</com.google.android.exoplayer2.ui.AspectRatioFrameLayout>
|
||||
|
||||
<com.google.android.exoplayer2.ui.PlaybackControlView android:id="@+id/control"
|
||||
<View android:id="@+id/exo_controller_placeholder"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"/>
|
||||
|
||||
|
@ -23,6 +23,7 @@
|
||||
<attr name="show_timeout" format="integer"/>
|
||||
<attr name="rewind_increment" format="integer"/>
|
||||
<attr name="fastforward_increment" format="integer"/>
|
||||
<attr name="controller_layout_id" format="reference"/>
|
||||
|
||||
<declare-styleable name="SimpleExoPlayerView">
|
||||
<attr name="use_controller" format="boolean"/>
|
||||
@ -31,6 +32,7 @@
|
||||
<attr name="rewind_increment"/>
|
||||
<attr name="fastforward_increment"/>
|
||||
<attr name="resize_mode"/>
|
||||
<attr name="controller_layout_id"/>
|
||||
</declare-styleable>
|
||||
|
||||
<declare-styleable name="AspectRatioFrameLayout">
|
||||
@ -41,6 +43,7 @@
|
||||
<attr name="show_timeout"/>
|
||||
<attr name="rewind_increment"/>
|
||||
<attr name="fastforward_increment"/>
|
||||
<attr name="controller_layout_id"/>
|
||||
</declare-styleable>
|
||||
|
||||
</resources>
|
||||
|
26
library/src/main/res/values/ids.xml
Normal file
26
library/src/main/res/values/ids.xml
Normal file
@ -0,0 +1,26 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- 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.
|
||||
-->
|
||||
<resources>
|
||||
<item name="exo_play" type="id"/>
|
||||
<item name="exo_pause" type="id"/>
|
||||
<item name="exo_rew" type="id"/>
|
||||
<item name="exo_ffwd" type="id"/>
|
||||
<item name="exo_prev" type="id"/>
|
||||
<item name="exo_next" type="id"/>
|
||||
<item name="exo_time" type="id"/>
|
||||
<item name="exo_time_current" type="id"/>
|
||||
<item name="exo_progress" type="id"/>
|
||||
</resources>
|
@ -41,4 +41,14 @@
|
||||
<item name="android:contentDescription">@string/exo_controls_rewind_description</item>
|
||||
</style>
|
||||
|
||||
<style name="ExoMediaButton.Play">
|
||||
<item name="android:src">@drawable/exo_controls_play</item>
|
||||
<item name="android:contentDescription">@string/exo_controls_play_description</item>
|
||||
</style>
|
||||
|
||||
<style name="ExoMediaButton.Pause">
|
||||
<item name="android:src">@drawable/exo_controls_pause</item>
|
||||
<item name="android:contentDescription">@string/exo_controls_pause_description</item>
|
||||
</style>
|
||||
|
||||
</resources>
|
||||
|
@ -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<HostActivit
|
||||
|
||||
private DashTestTrackSelector(String audioFormatId, String[] videoFormatIds,
|
||||
boolean canIncludeAdditionalVideoFormats) {
|
||||
super(new Handler(Looper.getMainLooper()));
|
||||
this.audioFormatId = audioFormatId;
|
||||
this.videoFormatIds = videoFormatIds;
|
||||
this.canIncludeAdditionalVideoFormats = canIncludeAdditionalVideoFormats;
|
||||
|
@ -33,9 +33,11 @@ import com.google.android.exoplayer2.drm.DrmSessionManager;
|
||||
import com.google.android.exoplayer2.drm.FrameworkMediaCrypto;
|
||||
import com.google.android.exoplayer2.playbacktests.util.HostActivity.HostedTest;
|
||||
import com.google.android.exoplayer2.source.MediaSource;
|
||||
import com.google.android.exoplayer2.source.TrackGroupArray;
|
||||
import com.google.android.exoplayer2.trackselection.AdaptiveVideoTrackSelection;
|
||||
import com.google.android.exoplayer2.trackselection.DefaultTrackSelector;
|
||||
import com.google.android.exoplayer2.trackselection.MappingTrackSelector;
|
||||
import com.google.android.exoplayer2.trackselection.TrackSelectionArray;
|
||||
import com.google.android.exoplayer2.upstream.BandwidthMeter;
|
||||
import com.google.android.exoplayer2.upstream.DataSource;
|
||||
import com.google.android.exoplayer2.upstream.DefaultBandwidthMeter;
|
||||
@ -186,6 +188,11 @@ public abstract class ExoHostedTest implements HostedTest, ExoPlayer.EventListen
|
||||
// Do nothing.
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTracksChanged(TrackGroupArray trackGroups, TrackSelectionArray trackSelections) {
|
||||
// Do nothing.
|
||||
}
|
||||
|
||||
@Override
|
||||
public final void onPlayerStateChanged(boolean playWhenReady, int playbackState) {
|
||||
Log.d(tag, "state [" + playWhenReady + ", " + playbackState + "]");
|
||||
@ -305,7 +312,7 @@ public abstract class ExoHostedTest implements HostedTest, ExoPlayer.EventListen
|
||||
@SuppressWarnings("unused")
|
||||
protected MappingTrackSelector buildTrackSelector(HostActivity host,
|
||||
BandwidthMeter bandwidthMeter) {
|
||||
return new DefaultTrackSelector(null, new AdaptiveVideoTrackSelection.Factory(bandwidthMeter));
|
||||
return new DefaultTrackSelector(new AdaptiveVideoTrackSelection.Factory(bandwidthMeter));
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
|
Loading…
x
Reference in New Issue
Block a user