Common base class for SampleSource based TrackRenderers.

This will allow multi-track support when consuming from a SampleSource to
be added in a single class, rather than in 6. Prep for Issue #514.
This commit is contained in:
Oliver Woodman 2015-08-11 18:04:00 +01:00
parent 150b3cdb19
commit 13f4a3e3bd
13 changed files with 291 additions and 383 deletions

View File

@ -21,7 +21,8 @@ import com.google.android.exoplayer.MediaClock;
import com.google.android.exoplayer.MediaFormat;
import com.google.android.exoplayer.MediaFormatHolder;
import com.google.android.exoplayer.SampleSource;
import com.google.android.exoplayer.SampleSource.SampleSourceReader;
import com.google.android.exoplayer.SampleSourceTrackRenderer;
import com.google.android.exoplayer.TrackInfo;
import com.google.android.exoplayer.TrackRenderer;
import com.google.android.exoplayer.audio.AudioTrack;
import com.google.android.exoplayer.ext.opus.OpusDecoderWrapper.InputBuffer;
@ -30,7 +31,6 @@ import com.google.android.exoplayer.util.MimeTypes;
import android.os.Handler;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.List;
@ -39,7 +39,8 @@ import java.util.List;
*
* @author vigneshv@google.com (Vignesh Venkatasubramanian)
*/
public class LibopusAudioTrackRenderer extends TrackRenderer implements MediaClock {
public final class LibopusAudioTrackRenderer extends SampleSourceTrackRenderer
implements MediaClock {
/**
* Interface definition for a callback to be notified of {@link LibopusAudioTrackRenderer} events.
@ -76,7 +77,6 @@ public class LibopusAudioTrackRenderer extends TrackRenderer implements MediaClo
*/
public static final int MSG_SET_VOLUME = 1;
private final SampleSourceReader source;
private final Handler eventHandler;
private final EventListener eventListener;
private final MediaFormatHolder formatHolder;
@ -86,7 +86,6 @@ public class LibopusAudioTrackRenderer extends TrackRenderer implements MediaClo
private InputBuffer inputBuffer;
private OutputBuffer outputBuffer;
private int trackIndex;
private long currentPositionUs;
private boolean allowPositionDiscontinuity;
private boolean inputStreamEnded;
@ -112,7 +111,7 @@ public class LibopusAudioTrackRenderer extends TrackRenderer implements MediaClo
*/
public LibopusAudioTrackRenderer(SampleSource source, Handler eventHandler,
EventListener eventListener) {
this.source = source.register();
super(source);
this.eventHandler = eventHandler;
this.eventListener = eventListener;
this.audioSessionId = AudioTrack.SESSION_ID_NOT_SET;
@ -126,20 +125,15 @@ public class LibopusAudioTrackRenderer extends TrackRenderer implements MediaClo
}
@Override
protected int doPrepare(long positionUs) {
boolean sourcePrepared = source.prepare(positionUs);
if (!sourcePrepared) {
return TrackRenderer.STATE_UNPREPARED;
protected boolean handlesTrack(TrackInfo trackInfo) {
return MimeTypes.AUDIO_OPUS.equalsIgnoreCase(trackInfo.mimeType)
|| MimeTypes.AUDIO_WEBM.equalsIgnoreCase(trackInfo.mimeType);
}
int trackCount = source.getTrackCount();
for (int i = 0; i < trackCount; i++) {
if (source.getTrackInfo(i).mimeType.equalsIgnoreCase(MimeTypes.AUDIO_OPUS)
|| source.getTrackInfo(i).mimeType.equalsIgnoreCase(MimeTypes.AUDIO_WEBM)) {
trackIndex = i;
return TrackRenderer.STATE_PREPARED;
}
}
return TrackRenderer.STATE_IGNORE;
@Override
protected void onEnabled(long positionUs, boolean joining) throws ExoPlaybackException {
super.onEnabled(positionUs, joining);
seekToInternal(positionUs);
}
@Override
@ -147,8 +141,8 @@ public class LibopusAudioTrackRenderer extends TrackRenderer implements MediaClo
if (outputStreamEnded) {
return;
}
sourceIsReady = source.continueBuffering(trackIndex, positionUs);
checkForDiscontinuity();
sourceIsReady = continueBufferingSource(positionUs);
checkForDiscontinuity(positionUs);
// Try and read a format if we don't have one already.
if (format == null && !readFormat(positionUs)) {
@ -189,7 +183,7 @@ public class LibopusAudioTrackRenderer extends TrackRenderer implements MediaClo
// Rendering loop.
try {
renderBuffer();
while (feedInputBuffer()) {}
while (feedInputBuffer(positionUs)) {}
} catch (AudioTrack.InitializationException e) {
notifyAudioTrackInitializationError(e);
throw new ExoPlaybackException(e);
@ -249,7 +243,7 @@ public class LibopusAudioTrackRenderer extends TrackRenderer implements MediaClo
}
}
private boolean feedInputBuffer() throws OpusDecoderException {
private boolean feedInputBuffer(long positionUs) throws OpusDecoderException {
if (inputStreamEnded) {
return false;
}
@ -261,8 +255,7 @@ public class LibopusAudioTrackRenderer extends TrackRenderer implements MediaClo
}
}
int result = source.readData(trackIndex, currentPositionUs, formatHolder,
inputBuffer.sampleHolder, false);
int result = readSource(positionUs, formatHolder, inputBuffer.sampleHolder, false);
if (result == SampleSource.NOTHING_READ) {
return false;
}
@ -291,11 +284,11 @@ public class LibopusAudioTrackRenderer extends TrackRenderer implements MediaClo
return true;
}
private void checkForDiscontinuity() {
private void checkForDiscontinuity(long positionUs) {
if (decoder == null) {
return;
}
int result = source.readData(trackIndex, currentPositionUs, formatHolder, null, true);
int result = readSource(positionUs, formatHolder, null, true);
if (result == SampleSource.DISCONTINUITY_READ) {
flushDecoder();
}
@ -319,11 +312,6 @@ public class LibopusAudioTrackRenderer extends TrackRenderer implements MediaClo
return audioTrack.hasPendingData() || (format != null && sourceIsReady);
}
@Override
protected long getDurationUs() {
return source.getTrackInfo(trackIndex).durationUs;
}
@Override
public long getPositionUs() {
long newCurrentPositionUs = audioTrack.getCurrentPositionUs(isEnded());
@ -335,14 +323,9 @@ public class LibopusAudioTrackRenderer extends TrackRenderer implements MediaClo
return currentPositionUs;
}
@Override
protected long getBufferedPositionUs() {
return source.getBufferedPositionUs();
}
@Override
protected void seekTo(long positionUs) throws ExoPlaybackException {
source.seekToUs(positionUs);
super.seekTo(positionUs);
seekToInternal(positionUs);
}
@ -350,18 +333,11 @@ public class LibopusAudioTrackRenderer extends TrackRenderer implements MediaClo
audioTrack.reset();
currentPositionUs = positionUs;
allowPositionDiscontinuity = true;
source.seekToUs(positionUs);
inputStreamEnded = false;
outputStreamEnded = false;
sourceIsReady = false;
}
@Override
protected void onEnabled(long positionUs, boolean joining) {
source.enable(trackIndex, positionUs);
seekToInternal(positionUs);
}
@Override
protected void onStarted() {
audioTrack.play();
@ -373,38 +349,24 @@ public class LibopusAudioTrackRenderer extends TrackRenderer implements MediaClo
}
@Override
protected void onReleased() {
source.release();
}
@Override
protected void onDisabled() {
protected void onDisabled() throws ExoPlaybackException {
inputBuffer = null;
outputBuffer = null;
format = null;
audioSessionId = AudioTrack.SESSION_ID_NOT_SET;
try {
if (decoder != null) {
decoder.release();
decoder = null;
}
audioSessionId = AudioTrack.SESSION_ID_NOT_SET;
try {
audioTrack.release();
} finally {
inputBuffer = null;
outputBuffer = null;
format = null;
source.disable(trackIndex);
}
}
@Override
protected void maybeThrowError() throws ExoPlaybackException {
try {
source.maybeThrowError();
} catch (IOException e) {
throw new ExoPlaybackException(e);
super.onDisabled();
}
}
private boolean readFormat(long positionUs) {
int result = source.readData(trackIndex, positionUs, formatHolder, null, false);
int result = readSource(positionUs, formatHolder, null, false);
if (result == SampleSource.FORMAT_READ) {
format = formatHolder.format;
audioTrack.reconfigure(format.getFrameworkMediaFormatV16());

View File

@ -20,7 +20,8 @@ import com.google.android.exoplayer.ExoPlayer;
import com.google.android.exoplayer.MediaFormat;
import com.google.android.exoplayer.MediaFormatHolder;
import com.google.android.exoplayer.SampleSource;
import com.google.android.exoplayer.SampleSource.SampleSourceReader;
import com.google.android.exoplayer.SampleSourceTrackRenderer;
import com.google.android.exoplayer.TrackInfo;
import com.google.android.exoplayer.TrackRenderer;
import com.google.android.exoplayer.ext.vp9.VpxDecoderWrapper.InputBuffer;
import com.google.android.exoplayer.ext.vp9.VpxDecoderWrapper.OutputBuffer;
@ -32,12 +33,10 @@ import android.os.Handler;
import android.os.SystemClock;
import android.view.Surface;
import java.io.IOException;
/**
* Decodes and renders video using the native VP9 decoder.
*/
public class LibvpxVideoTrackRenderer extends TrackRenderer {
public final class LibvpxVideoTrackRenderer extends SampleSourceTrackRenderer {
/**
* Interface definition for a callback to be notified of {@link LibvpxVideoTrackRenderer} events.
@ -91,7 +90,6 @@ public class LibvpxVideoTrackRenderer extends TrackRenderer {
public static final int MSG_SET_SURFACE = 1;
public static final int MSG_SET_VPX_SURFACE_VIEW = 2;
private final SampleSourceReader source;
private final boolean scaleToFit;
private final Handler eventHandler;
private final EventListener eventListener;
@ -110,7 +108,6 @@ public class LibvpxVideoTrackRenderer extends TrackRenderer {
private VpxVideoSurfaceView vpxVideoSurfaceView;
private boolean outputRgb;
private int trackIndex;
private boolean inputStreamEnded;
private boolean outputStreamEnded;
private boolean sourceIsReady;
@ -141,7 +138,7 @@ public class LibvpxVideoTrackRenderer extends TrackRenderer {
*/
public LibvpxVideoTrackRenderer(SampleSource source, boolean scaleToFit,
Handler eventHandler, EventListener eventListener, int maxDroppedFrameCountToNotify) {
this.source = source.register();
super(source);
this.scaleToFit = scaleToFit;
this.eventHandler = eventHandler;
this.eventListener = eventListener;
@ -152,20 +149,9 @@ public class LibvpxVideoTrackRenderer extends TrackRenderer {
}
@Override
protected int doPrepare(long positionUs) throws ExoPlaybackException {
boolean sourcePrepared = source.prepare(positionUs);
if (!sourcePrepared) {
return TrackRenderer.STATE_UNPREPARED;
}
int trackCount = source.getTrackCount();
for (int i = 0; i < trackCount; i++) {
if (source.getTrackInfo(i).mimeType.equalsIgnoreCase(MimeTypes.VIDEO_VP9)
|| source.getTrackInfo(i).mimeType.equalsIgnoreCase(MimeTypes.VIDEO_WEBM)) {
trackIndex = i;
return TrackRenderer.STATE_PREPARED;
}
}
return TrackRenderer.STATE_IGNORE;
protected boolean handlesTrack(TrackInfo trackInfo) {
return MimeTypes.VIDEO_VP9.equalsIgnoreCase(trackInfo.mimeType)
|| MimeTypes.VIDEO_WEBM.equalsIgnoreCase(trackInfo.mimeType);
}
@Override
@ -173,7 +159,7 @@ public class LibvpxVideoTrackRenderer extends TrackRenderer {
if (outputStreamEnded) {
return;
}
sourceIsReady = source.continueBuffering(trackIndex, positionUs);
sourceIsReady = continueBufferingSource(positionUs);
checkForDiscontinuity(positionUs);
// Try and read a format if we don't have one already.
@ -302,7 +288,7 @@ public class LibvpxVideoTrackRenderer extends TrackRenderer {
}
}
int result = source.readData(trackIndex, positionUs, formatHolder, inputBuffer.sampleHolder,
int result = readSource(positionUs, formatHolder, inputBuffer.sampleHolder,
false);
if (result == SampleSource.NOTHING_READ) {
return false;
@ -334,7 +320,7 @@ public class LibvpxVideoTrackRenderer extends TrackRenderer {
if (decoder == null) {
return;
}
int result = source.readData(trackIndex, positionUs, formatHolder, null, true);
int result = readSource(positionUs, formatHolder, null, true);
if (result == SampleSource.DISCONTINUITY_READ) {
flushDecoder();
}
@ -356,25 +342,15 @@ public class LibvpxVideoTrackRenderer extends TrackRenderer {
return format != null && sourceIsReady;
}
@Override
protected long getDurationUs() {
return source.getTrackInfo(trackIndex).durationUs;
}
@Override
protected long getBufferedPositionUs() {
return source.getBufferedPositionUs();
}
@Override
protected void seekTo(long positionUs) throws ExoPlaybackException {
source.seekToUs(positionUs);
super.seekTo(positionUs);
seekToInternal();
}
@Override
protected void onEnabled(long positionUs, boolean joining) {
source.enable(trackIndex, positionUs);
protected void onEnabled(long positionUs, boolean joining) throws ExoPlaybackException {
super.onEnabled(positionUs, joining);
seekToInternal();
}
@ -397,33 +373,22 @@ public class LibvpxVideoTrackRenderer extends TrackRenderer {
}
@Override
protected void onReleased() {
source.release();
}
@Override
protected void onDisabled() {
protected void onDisabled() throws ExoPlaybackException {
inputBuffer = null;
outputBuffer = null;
format = null;
try {
if (decoder != null) {
decoder.release();
decoder = null;
}
inputBuffer = null;
outputBuffer = null;
format = null;
source.disable(trackIndex);
}
@Override
protected void maybeThrowError() throws ExoPlaybackException {
try {
source.maybeThrowError();
} catch (IOException e) {
throw new ExoPlaybackException(e);
} finally {
super.onDisabled();
}
}
private boolean readFormat(long positionUs) {
int result = source.readData(trackIndex, positionUs, formatHolder, null, false);
int result = readSource(positionUs, formatHolder, null, false);
if (result == SampleSource.FORMAT_READ) {
format = formatHolder.format;
return true;

View File

@ -157,12 +157,12 @@ public class MediaCodecAudioTrackRenderer extends MediaCodecTrackRenderer implem
}
@Override
protected boolean handlesMimeType(String mimeType) {
return MimeTypes.isAudio(mimeType) && super.handlesMimeType(mimeType);
protected boolean handlesTrack(TrackInfo trackInfo) {
return MimeTypes.isAudio(trackInfo.mimeType);
}
@Override
protected void onEnabled(long positionUs, boolean joining) {
protected void onEnabled(long positionUs, boolean joining) throws ExoPlaybackException {
super.onEnabled(positionUs, joining);
seekToInternal(positionUs);
}
@ -231,7 +231,7 @@ public class MediaCodecAudioTrackRenderer extends MediaCodecTrackRenderer implem
}
@Override
protected void onDisabled() {
protected void onDisabled() throws ExoPlaybackException {
audioSessionId = AudioTrack.SESSION_ID_NOT_SET;
try {
audioTrack.release();

View File

@ -16,7 +16,6 @@
package com.google.android.exoplayer;
import com.google.android.exoplayer.MediaCodecUtil.DecoderQueryException;
import com.google.android.exoplayer.SampleSource.SampleSourceReader;
import com.google.android.exoplayer.drm.DrmInitData;
import com.google.android.exoplayer.drm.DrmSessionManager;
import com.google.android.exoplayer.util.Assertions;
@ -31,7 +30,6 @@ import android.media.MediaCrypto;
import android.os.Handler;
import android.os.SystemClock;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.List;
@ -40,7 +38,7 @@ import java.util.List;
* An abstract {@link TrackRenderer} that uses {@link MediaCodec} to decode samples for rendering.
*/
@TargetApi(16)
public abstract class MediaCodecTrackRenderer extends TrackRenderer {
public abstract class MediaCodecTrackRenderer extends SampleSourceTrackRenderer {
/**
* Interface definition for a callback to be notified of {@link MediaCodecTrackRenderer} events.
@ -183,7 +181,6 @@ public abstract class MediaCodecTrackRenderer extends TrackRenderer {
private final DrmSessionManager drmSessionManager;
private final boolean playClearSamplesWithoutKeys;
private final SampleSourceReader source;
private final SampleHolder sampleHolder;
private final MediaFormatHolder formatHolder;
private final List<Long> decodeOnlyPresentationTimestamps;
@ -207,7 +204,6 @@ public abstract class MediaCodecTrackRenderer extends TrackRenderer {
private int codecReinitializationState;
private boolean codecHasQueuedBuffers;
private int trackIndex;
private int sourceState;
private boolean inputStreamEnded;
private boolean outputStreamEnded;
@ -229,8 +225,8 @@ public abstract class MediaCodecTrackRenderer extends TrackRenderer {
*/
public MediaCodecTrackRenderer(SampleSource source, DrmSessionManager drmSessionManager,
boolean playClearSamplesWithoutKeys, Handler eventHandler, EventListener eventListener) {
super(source);
Assertions.checkState(Util.SDK_INT >= 16);
this.source = source.register();
this.drmSessionManager = drmSessionManager;
this.playClearSamplesWithoutKeys = playClearSamplesWithoutKeys;
this.eventHandler = eventHandler;
@ -245,39 +241,8 @@ public abstract class MediaCodecTrackRenderer extends TrackRenderer {
}
@Override
protected int doPrepare(long positionUs) {
boolean sourcePrepared = source.prepare(positionUs);
if (!sourcePrepared) {
return TrackRenderer.STATE_UNPREPARED;
}
int trackCount = source.getTrackCount();
for (int i = 0; i < trackCount; i++) {
// TODO: Right now this is getting the mime types of the container format
// (e.g. audio/mp4 and video/mp4 for fragmented mp4). It needs to be getting the mime types
// of the actual samples (e.g. audio/mp4a-latm and video/avc).
if (handlesMimeType(source.getTrackInfo(i).mimeType)) {
trackIndex = i;
return TrackRenderer.STATE_PREPARED;
}
}
return TrackRenderer.STATE_IGNORE;
}
/**
* Determines whether a mime type is handled by the renderer.
*
* @param mimeType The mime type to test.
* @return True if the renderer can handle the mime type. False otherwise.
*/
protected boolean handlesMimeType(String mimeType) {
return true;
// TODO: Uncomment once the TODO above is fixed.
// DecoderInfoUtil.getDecoder(mimeType) != null;
}
@Override
protected void onEnabled(long positionUs, boolean joining) {
source.enable(trackIndex, positionUs);
protected void onEnabled(long positionUs, boolean joining) throws ExoPlaybackException {
super.onEnabled(positionUs, joining);
seekToInternal();
}
@ -400,7 +365,7 @@ public abstract class MediaCodecTrackRenderer extends TrackRenderer {
}
@Override
protected void onDisabled() {
protected void onDisabled() throws ExoPlaybackException {
format = null;
drmInitData = null;
try {
@ -412,7 +377,7 @@ public abstract class MediaCodecTrackRenderer extends TrackRenderer {
openedDrmSession = false;
}
} finally {
source.disable(trackIndex);
super.onDisabled();
}
}
}
@ -445,24 +410,9 @@ public abstract class MediaCodecTrackRenderer extends TrackRenderer {
}
}
@Override
protected void onReleased() {
source.release();
}
@Override
protected long getDurationUs() {
return source.getTrackInfo(trackIndex).durationUs;
}
@Override
protected long getBufferedPositionUs() {
return source.getBufferedPositionUs();
}
@Override
protected void seekTo(long positionUs) throws ExoPlaybackException {
source.seekToUs(positionUs);
super.seekTo(positionUs);
seekToInternal();
}
@ -484,7 +434,7 @@ public abstract class MediaCodecTrackRenderer extends TrackRenderer {
@Override
protected void doSomeWork(long positionUs, long elapsedRealtimeUs) throws ExoPlaybackException {
sourceState = source.continueBuffering(trackIndex, positionUs)
sourceState = continueBufferingSource(positionUs)
? (sourceState == SOURCE_STATE_NOT_READY ? SOURCE_STATE_READY : sourceState)
: SOURCE_STATE_NOT_READY;
checkForDiscontinuity(positionUs);
@ -506,7 +456,7 @@ public abstract class MediaCodecTrackRenderer extends TrackRenderer {
}
private void readFormat(long positionUs) throws ExoPlaybackException {
int result = source.readData(trackIndex, positionUs, formatHolder, sampleHolder, false);
int result = readSource(positionUs, formatHolder, sampleHolder, false);
if (result == SampleSource.FORMAT_READ) {
onInputFormatChanged(formatHolder);
}
@ -516,7 +466,7 @@ public abstract class MediaCodecTrackRenderer extends TrackRenderer {
if (codec == null) {
return;
}
int result = source.readData(trackIndex, positionUs, formatHolder, sampleHolder, true);
int result = readSource(positionUs, formatHolder, sampleHolder, true);
if (result == SampleSource.DISCONTINUITY_READ) {
flushCodec();
}
@ -597,7 +547,7 @@ public abstract class MediaCodecTrackRenderer extends TrackRenderer {
}
codecReconfigurationState = RECONFIGURATION_STATE_QUEUE_PENDING;
}
result = source.readData(trackIndex, positionUs, formatHolder, sampleHolder, false);
result = readSource(positionUs, formatHolder, sampleHolder, false);
if (firstFeed && sourceState == SOURCE_STATE_READY && result == SampleSource.NOTHING_READ) {
sourceState = SOURCE_STATE_READY_READ_MAY_FAIL;
}
@ -774,15 +724,6 @@ public abstract class MediaCodecTrackRenderer extends TrackRenderer {
return false;
}
@Override
protected void maybeThrowError() throws ExoPlaybackException {
try {
source.maybeThrowError();
} catch (IOException e) {
throw new ExoPlaybackException(e);
}
}
@Override
protected boolean isEnded() {
return outputStreamEnded;
@ -950,11 +891,8 @@ public abstract class MediaCodecTrackRenderer extends TrackRenderer {
* incorrectly on the host device. False otherwise.
*/
private static boolean codecNeedsEndOfStreamWorkaround(String name) {
return Util.SDK_INT <= 17
&& "OMX.rk.video_decoder.avc".equals(name)
&& ("ht7s3".equals(Util.DEVICE) // Tesco HUDL
|| "rk30sdk".equals(Util.DEVICE) // Rockchip rk30
|| "rk31sdk".equals(Util.DEVICE)); // Rockchip rk31
return Util.SDK_INT <= 17 && "ht7s3".equals(Util.DEVICE) // Tesco HUDL
&& "OMX.rk.video_decoder.avc".equals(name);
}
}

View File

@ -256,12 +256,12 @@ public class MediaCodecVideoTrackRenderer extends MediaCodecTrackRenderer {
}
@Override
protected boolean handlesMimeType(String mimeType) {
return MimeTypes.isVideo(mimeType) && super.handlesMimeType(mimeType);
protected boolean handlesTrack(TrackInfo trackInfo) {
return MimeTypes.isVideo(trackInfo.mimeType);
}
@Override
protected void onEnabled(long positionUs, boolean joining) {
protected void onEnabled(long positionUs, boolean joining) throws ExoPlaybackException {
super.onEnabled(positionUs, joining);
renderedFirstFrame = false;
if (joining && allowedJoiningTimeUs > 0) {
@ -314,7 +314,7 @@ public class MediaCodecVideoTrackRenderer extends MediaCodecTrackRenderer {
}
@Override
public void onDisabled() {
protected void onDisabled() throws ExoPlaybackException {
currentWidth = -1;
currentHeight = -1;
currentPixelWidthHeightRatio = -1;

View File

@ -0,0 +1,123 @@
/*
* Copyright (C) 2014 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.android.exoplayer;
import com.google.android.exoplayer.SampleSource.SampleSourceReader;
import java.io.IOException;
/**
* Base class for {@link TrackRenderer} implementations that render samples obtained from a
* {@link SampleSource}.
*/
public abstract class SampleSourceTrackRenderer extends TrackRenderer {
private final SampleSourceReader source;
private int trackIndex;
/**
* @param source The upstream source from which the renderer obtains samples.
*/
public SampleSourceTrackRenderer(SampleSource source) {
this.source = source.register();
}
@Override
protected int doPrepare(long positionUs) throws ExoPlaybackException {
boolean sourcePrepared = source.prepare(positionUs);
if (!sourcePrepared) {
return TrackRenderer.STATE_UNPREPARED;
}
int trackCount = source.getTrackCount();
for (int i = 0; i < trackCount; i++) {
TrackInfo trackInfo = source.getTrackInfo(i);
if (handlesTrack(trackInfo)) {
trackIndex = i;
onTrackSelected(trackInfo);
return TrackRenderer.STATE_PREPARED;
}
}
return TrackRenderer.STATE_IGNORE;
}
/**
* Returns whether this renderer is capable of handling the provided track.
*
* @param trackInfo The track.
* @return True if the renderer can handle the track, false otherwise.
*/
protected abstract boolean handlesTrack(TrackInfo trackInfo);
/**
* Invoked when a track is selected.
*
* @param trackInfo The selected track.
*/
protected void onTrackSelected(TrackInfo trackInfo) {
// Do nothing.
}
@Override
protected void onEnabled(long positionUs, boolean joining) throws ExoPlaybackException {
source.enable(trackIndex, positionUs);
}
@Override
protected void seekTo(long positionUs) throws ExoPlaybackException {
source.seekToUs(positionUs);
}
@Override
protected long getBufferedPositionUs() {
return source.getBufferedPositionUs();
}
@Override
protected long getDurationUs() {
return source.getTrackInfo(trackIndex).durationUs;
}
@Override
protected void maybeThrowError() throws ExoPlaybackException {
try {
source.maybeThrowError();
} catch (IOException e) {
throw new ExoPlaybackException(e);
}
}
@Override
protected void onDisabled() throws ExoPlaybackException {
source.disable(trackIndex);
}
@Override
protected void onReleased() throws ExoPlaybackException {
source.release();
}
protected final boolean continueBufferingSource(long positionUs) {
return source.continueBuffering(trackIndex, positionUs);
}
protected final int readSource(long positionUs, MediaFormatHolder formatHolder,
SampleHolder sampleHolder, boolean onlyReadDiscontinuity) {
return source.readData(trackIndex, positionUs, formatHolder, sampleHolder,
onlyReadDiscontinuity);
}
}

View File

@ -110,11 +110,11 @@ public abstract class TrackRenderer implements ExoPlayerComponent {
* @throws ExoPlaybackException If an error occurs.
*/
/* package */ final int prepare(long positionUs) throws ExoPlaybackException {
Assertions.checkState(state == TrackRenderer.STATE_UNPREPARED);
Assertions.checkState(state == STATE_UNPREPARED);
state = doPrepare(positionUs);
Assertions.checkState(state == TrackRenderer.STATE_UNPREPARED ||
state == TrackRenderer.STATE_PREPARED ||
state == TrackRenderer.STATE_IGNORE);
Assertions.checkState(state == STATE_UNPREPARED ||
state == STATE_PREPARED ||
state == STATE_IGNORE);
return state;
}
@ -143,8 +143,8 @@ public abstract class TrackRenderer implements ExoPlayerComponent {
* @throws ExoPlaybackException If an error occurs.
*/
/* package */ final void enable(long positionUs, boolean joining) throws ExoPlaybackException {
Assertions.checkState(state == TrackRenderer.STATE_PREPARED);
state = TrackRenderer.STATE_ENABLED;
Assertions.checkState(state == STATE_PREPARED);
state = STATE_ENABLED;
onEnabled(positionUs, joining);
}
@ -170,8 +170,8 @@ public abstract class TrackRenderer implements ExoPlayerComponent {
* @throws ExoPlaybackException If an error occurs.
*/
/* package */ final void start() throws ExoPlaybackException {
Assertions.checkState(state == TrackRenderer.STATE_ENABLED);
state = TrackRenderer.STATE_STARTED;
Assertions.checkState(state == STATE_ENABLED);
state = STATE_STARTED;
onStarted();
}
@ -192,8 +192,8 @@ public abstract class TrackRenderer implements ExoPlayerComponent {
* @throws ExoPlaybackException If an error occurs.
*/
/* package */ final void stop() throws ExoPlaybackException {
Assertions.checkState(state == TrackRenderer.STATE_STARTED);
state = TrackRenderer.STATE_ENABLED;
Assertions.checkState(state == STATE_STARTED);
state = STATE_ENABLED;
onStopped();
}
@ -214,8 +214,8 @@ public abstract class TrackRenderer implements ExoPlayerComponent {
* @throws ExoPlaybackException If an error occurs.
*/
/* package */ final void disable() throws ExoPlaybackException {
Assertions.checkState(state == TrackRenderer.STATE_ENABLED);
state = TrackRenderer.STATE_PREPARED;
Assertions.checkState(state == STATE_ENABLED);
state = STATE_PREPARED;
onDisabled();
}
@ -236,10 +236,10 @@ public abstract class TrackRenderer implements ExoPlayerComponent {
* @throws ExoPlaybackException If an error occurs.
*/
/* package */ final void release() throws ExoPlaybackException {
Assertions.checkState(state != TrackRenderer.STATE_ENABLED
&& state != TrackRenderer.STATE_STARTED
&& state != TrackRenderer.STATE_RELEASED);
state = TrackRenderer.STATE_RELEASED;
Assertions.checkState(state != STATE_ENABLED
&& state != STATE_STARTED
&& state != STATE_RELEASED);
state = STATE_RELEASED;
onReleased();
}

View File

@ -58,7 +58,7 @@ public class ChunkSampleSource implements SampleSource, SampleSourceReader, Load
private static final int STATE_PREPARED = 2;
private static final int STATE_ENABLED = 3;
private static final int NO_RESET_PENDING = -1;
private static final long NO_RESET_PENDING = Long.MIN_VALUE;
private final int eventSourceId;
private final LoadControl loadControl;

View File

@ -93,7 +93,7 @@ public final class ExtractorSampleSource implements SampleSource, SampleSourceRe
public static final int DEFAULT_MIN_LOADABLE_RETRY_COUNT_LIVE = 6;
private static final int MIN_RETRY_COUNT_DEFAULT_FOR_MEDIA = -1;
private static final int NO_RESET_PENDING = -1;
private static final long NO_RESET_PENDING = Long.MIN_VALUE;
/**
* Default extractor classes in priority order. They are referred to indirectly so that it is
@ -326,10 +326,14 @@ public final class ExtractorSampleSource implements SampleSource, SampleSourceRe
enabledTrackCount++;
trackEnabledStates[track] = true;
pendingMediaFormat[track] = true;
if (enabledTrackCount == 1) {
seekToUs(positionUs);
}
pendingDiscontinuities[track] = false;
if (enabledTrackCount == 1) {
// Treat all enables in non-seekable media as being from t=0.
positionUs = !seekMap.isSeekable() ? 0 : positionUs;
downstreamPositionUs = positionUs;
lastSeekPositionUs = positionUs;
restartFrom(positionUs);
}
}
@Override
@ -431,10 +435,8 @@ public final class ExtractorSampleSource implements SampleSource, SampleSourceRe
public void seekToUs(long positionUs) {
Assertions.checkState(prepared);
Assertions.checkState(enabledTrackCount > 0);
if (!seekMap.isSeekable()) {
// Treat all seeks into non-seekable media as seeks to the start.
positionUs = 0;
}
// Treat all seeks into non-seekable media as being to t=0.
positionUs = !seekMap.isSeekable() ? 0 : positionUs;
long currentPositionUs = isPendingReset() ? pendingResetPositionUs : downstreamPositionUs;
downstreamPositionUs = positionUs;

View File

@ -52,7 +52,7 @@ public final class HlsSampleSource implements SampleSource, SampleSourceReader,
*/
public static final int DEFAULT_MIN_LOADABLE_RETRY_COUNT = 3;
private static final int NO_RESET_PENDING = -1;
private static final long NO_RESET_PENDING = Long.MIN_VALUE;
private final HlsChunkSource chunkSource;
private final LinkedList<HlsExtractorWrapper> extractors;
@ -182,15 +182,17 @@ public final class HlsSampleSource implements SampleSource, SampleSourceReader,
enabledTrackCount++;
trackEnabledStates[track] = true;
downstreamMediaFormats[track] = null;
pendingDiscontinuities[track] = false;
downstreamFormat = null;
if (!loadControlRegistered) {
loadControl.register(this, bufferSizeContribution);
loadControlRegistered = true;
}
if (enabledTrackCount == 1) {
seekToUs(positionUs);
downstreamPositionUs = positionUs;
lastSeekPositionUs = positionUs;
restartFrom(positionUs);
}
pendingDiscontinuities[track] = false;
}
@Override

View File

@ -19,7 +19,8 @@ import com.google.android.exoplayer.ExoPlaybackException;
import com.google.android.exoplayer.MediaFormatHolder;
import com.google.android.exoplayer.SampleHolder;
import com.google.android.exoplayer.SampleSource;
import com.google.android.exoplayer.SampleSource.SampleSourceReader;
import com.google.android.exoplayer.SampleSourceTrackRenderer;
import com.google.android.exoplayer.TrackInfo;
import com.google.android.exoplayer.TrackRenderer;
import com.google.android.exoplayer.util.Assertions;
@ -35,7 +36,7 @@ import java.io.IOException;
*
* @param <T> The type of the metadata.
*/
public final class MetadataTrackRenderer<T> extends TrackRenderer implements Callback {
public final class MetadataTrackRenderer<T> extends SampleSourceTrackRenderer implements Callback {
/**
* An interface for components that process metadata.
@ -55,16 +56,13 @@ public final class MetadataTrackRenderer<T> extends TrackRenderer implements Cal
private static final int MSG_INVOKE_RENDERER = 0;
private final SampleSourceReader source;
private final MetadataParser<T> metadataParser;
private final MetadataRenderer<T> metadataRenderer;
private final Handler metadataHandler;
private final MediaFormatHolder formatHolder;
private final SampleHolder sampleHolder;
private int trackIndex;
private boolean inputStreamEnded;
private long pendingMetadataTimestamp;
private T pendingMetadata;
@ -80,7 +78,7 @@ public final class MetadataTrackRenderer<T> extends TrackRenderer implements Cal
*/
public MetadataTrackRenderer(SampleSource source, MetadataParser<T> metadataParser,
MetadataRenderer<T> metadataRenderer, Looper metadataRendererLooper) {
this.source = source.register();
super(source);
this.metadataParser = Assertions.checkNotNull(metadataParser);
this.metadataRenderer = Assertions.checkNotNull(metadataRenderer);
this.metadataHandler = metadataRendererLooper == null ? null
@ -90,30 +88,19 @@ public final class MetadataTrackRenderer<T> extends TrackRenderer implements Cal
}
@Override
protected int doPrepare(long positionUs) {
boolean sourcePrepared = source.prepare(positionUs);
if (!sourcePrepared) {
return TrackRenderer.STATE_UNPREPARED;
}
int trackCount = source.getTrackCount();
for (int i = 0; i < trackCount; i++) {
if (metadataParser.canParse(source.getTrackInfo(i).mimeType)) {
trackIndex = i;
return TrackRenderer.STATE_PREPARED;
}
}
return TrackRenderer.STATE_IGNORE;
protected boolean handlesTrack(TrackInfo trackInfo) {
return metadataParser.canParse(trackInfo.mimeType);
}
@Override
protected void onEnabled(long positionUs, boolean joining) {
source.enable(trackIndex, positionUs);
protected void onEnabled(long positionUs, boolean joining) throws ExoPlaybackException {
super.onEnabled(positionUs, joining);
seekToInternal();
}
@Override
protected void seekTo(long positionUs) throws ExoPlaybackException {
source.seekToUs(positionUs);
super.seekTo(positionUs);
seekToInternal();
}
@ -123,12 +110,10 @@ public final class MetadataTrackRenderer<T> extends TrackRenderer implements Cal
}
@Override
protected void doSomeWork(long positionUs, long elapsedRealtimeUs)
throws ExoPlaybackException {
source.continueBuffering(trackIndex, positionUs);
protected void doSomeWork(long positionUs, long elapsedRealtimeUs) throws ExoPlaybackException {
continueBufferingSource(positionUs);
if (!inputStreamEnded && pendingMetadata == null) {
int result = source.readData(trackIndex, positionUs, formatHolder, sampleHolder, false);
int result = readSource(positionUs, formatHolder, sampleHolder, false);
if (result == SampleSource.SAMPLE_READ) {
pendingMetadataTimestamp = sampleHolder.timeUs;
try {
@ -149,23 +134,9 @@ public final class MetadataTrackRenderer<T> extends TrackRenderer implements Cal
}
@Override
protected void maybeThrowError() throws ExoPlaybackException {
try {
source.maybeThrowError();
} catch (IOException e) {
throw new ExoPlaybackException(e);
}
}
@Override
protected void onDisabled() {
protected void onDisabled() throws ExoPlaybackException {
pendingMetadata = null;
source.disable(trackIndex);
}
@Override
protected long getDurationUs() {
return source.getTrackInfo(trackIndex).durationUs;
super.onDisabled();
}
@Override

View File

@ -19,7 +19,8 @@ import com.google.android.exoplayer.ExoPlaybackException;
import com.google.android.exoplayer.MediaFormatHolder;
import com.google.android.exoplayer.SampleHolder;
import com.google.android.exoplayer.SampleSource;
import com.google.android.exoplayer.SampleSource.SampleSourceReader;
import com.google.android.exoplayer.SampleSourceTrackRenderer;
import com.google.android.exoplayer.TrackInfo;
import com.google.android.exoplayer.TrackRenderer;
import com.google.android.exoplayer.util.Assertions;
@ -58,7 +59,7 @@ import java.util.List;
* {@link SubtitleParser#canParse(String)} will be used.
*/
@TargetApi(16)
public final class TextTrackRenderer extends TrackRenderer implements Callback {
public final class TextTrackRenderer extends SampleSourceTrackRenderer implements Callback {
private static final int MSG_UPDATE_OVERLAY = 0;
@ -104,15 +105,11 @@ public final class TextTrackRenderer extends TrackRenderer implements Callback {
private final Handler textRendererHandler;
private final TextRenderer textRenderer;
private final SampleSourceReader source;
private final MediaFormatHolder formatHolder;
private final SubtitleParser[] subtitleParsers;
private int parserIndex;
private int trackIndex;
private boolean inputStreamEnded;
private Subtitle subtitle;
private Subtitle nextSubtitle;
private SubtitleParserHelper parserHelper;
@ -132,7 +129,7 @@ public final class TextTrackRenderer extends TrackRenderer implements Callback {
*/
public TextTrackRenderer(SampleSource source, TextRenderer textRenderer,
Looper textRendererLooper, SubtitleParser... subtitleParsers) {
this.source = source.register();
super(source);
this.textRenderer = Assertions.checkNotNull(textRenderer);
this.textRendererHandler = textRendererLooper == null ? null
: new Handler(textRendererLooper, this);
@ -153,27 +150,29 @@ public final class TextTrackRenderer extends TrackRenderer implements Callback {
}
@Override
protected int doPrepare(long positionUs) {
boolean sourcePrepared = source.prepare(positionUs);
if (!sourcePrepared) {
return TrackRenderer.STATE_UNPREPARED;
}
int trackCount = source.getTrackCount();
protected boolean handlesTrack(TrackInfo trackInfo) {
for (int i = 0; i < subtitleParsers.length; i++) {
for (int j = 0; j < trackCount; j++) {
if (subtitleParsers[i].canParse(source.getTrackInfo(j).mimeType)) {
parserIndex = i;
trackIndex = j;
return TrackRenderer.STATE_PREPARED;
if (subtitleParsers[i].canParse(trackInfo.mimeType)) {
return true;
}
}
}
return TrackRenderer.STATE_IGNORE;
return false;
}
@Override
protected void onEnabled(long positionUs, boolean joining) {
source.enable(trackIndex, positionUs);
protected void onTrackSelected(TrackInfo trackInfo) {
for (int i = 0; i < subtitleParsers.length; i++) {
if (subtitleParsers[i].canParse(trackInfo.mimeType)) {
parserIndex = i;
return;
}
}
throw new IllegalStateException("Invalid track selected");
}
@Override
protected void onEnabled(long positionUs, boolean joining) throws ExoPlaybackException {
super.onEnabled(positionUs, joining);
parserThread = new HandlerThread("textParser");
parserThread.start();
parserHelper = new SubtitleParserHelper(parserThread.getLooper(), subtitleParsers[parserIndex]);
@ -181,8 +180,8 @@ public final class TextTrackRenderer extends TrackRenderer implements Callback {
}
@Override
protected void seekTo(long positionUs) {
source.seekToUs(positionUs);
protected void seekTo(long positionUs) throws ExoPlaybackException {
super.seekTo(positionUs);
seekToInternal();
}
@ -196,8 +195,7 @@ public final class TextTrackRenderer extends TrackRenderer implements Callback {
@Override
protected void doSomeWork(long positionUs, long elapsedRealtimeUs) throws ExoPlaybackException {
source.continueBuffering(trackIndex, positionUs);
continueBufferingSource(positionUs);
if (nextSubtitle == null) {
try {
nextSubtitle = parserHelper.getAndClearResult();
@ -241,7 +239,7 @@ public final class TextTrackRenderer extends TrackRenderer implements Callback {
// Try and read the next subtitle from the source.
SampleHolder sampleHolder = parserHelper.getSampleHolder();
sampleHolder.clearData();
int result = source.readData(trackIndex, positionUs, formatHolder, sampleHolder, false);
int result = readSource(positionUs, formatHolder, sampleHolder, false);
if (result == SampleSource.SAMPLE_READ) {
parserHelper.startParseOperation();
} else if (result == SampleSource.END_OF_STREAM) {
@ -251,33 +249,14 @@ public final class TextTrackRenderer extends TrackRenderer implements Callback {
}
@Override
protected void onDisabled() {
protected void onDisabled() throws ExoPlaybackException {
subtitle = null;
nextSubtitle = null;
parserThread.quit();
parserThread = null;
parserHelper = null;
clearTextRenderer();
source.disable(trackIndex);
}
@Override
protected void onReleased() {
source.release();
}
@Override
protected void maybeThrowError() throws ExoPlaybackException {
try {
source.maybeThrowError();
} catch (IOException e) {
throw new ExoPlaybackException(e);
}
}
@Override
protected long getDurationUs() {
return source.getTrackInfo(trackIndex).durationUs;
super.onDisabled();
}
@Override

View File

@ -20,7 +20,8 @@ import com.google.android.exoplayer.ExoPlaybackException;
import com.google.android.exoplayer.MediaFormatHolder;
import com.google.android.exoplayer.SampleHolder;
import com.google.android.exoplayer.SampleSource;
import com.google.android.exoplayer.SampleSource.SampleSourceReader;
import com.google.android.exoplayer.SampleSourceTrackRenderer;
import com.google.android.exoplayer.TrackInfo;
import com.google.android.exoplayer.TrackRenderer;
import com.google.android.exoplayer.text.Cue;
import com.google.android.exoplayer.text.TextRenderer;
@ -32,14 +33,13 @@ import android.os.Handler.Callback;
import android.os.Looper;
import android.os.Message;
import java.io.IOException;
import java.util.Collections;
import java.util.TreeSet;
/**
* A {@link TrackRenderer} for EIA-608 closed captions in a media stream.
*/
public final class Eia608TrackRenderer extends TrackRenderer implements Callback {
public final class Eia608TrackRenderer extends SampleSourceTrackRenderer implements Callback {
private static final int MSG_INVOKE_RENDERER = 0;
@ -53,7 +53,6 @@ public final class Eia608TrackRenderer extends TrackRenderer implements Callback
// The maximum duration that captions are parsed ahead of the current position.
private static final int MAX_SAMPLE_READAHEAD_US = 5000000;
private final SampleSourceReader source;
private final Eia608Parser eia608Parser;
private final TextRenderer textRenderer;
private final Handler textRendererHandler;
@ -62,9 +61,7 @@ public final class Eia608TrackRenderer extends TrackRenderer implements Callback
private final StringBuilder captionStringBuilder;
private final TreeSet<ClosedCaptionList> pendingCaptionLists;
private int trackIndex;
private boolean inputStreamEnded;
private int captionMode;
private int captionRowCount;
private String caption;
@ -81,7 +78,7 @@ public final class Eia608TrackRenderer extends TrackRenderer implements Callback
*/
public Eia608TrackRenderer(SampleSource source, TextRenderer textRenderer,
Looper textRendererLooper) {
this.source = source.register();
super(source);
this.textRenderer = Assertions.checkNotNull(textRenderer);
textRendererHandler = textRendererLooper == null ? null : new Handler(textRendererLooper, this);
eia608Parser = new Eia608Parser();
@ -92,30 +89,19 @@ public final class Eia608TrackRenderer extends TrackRenderer implements Callback
}
@Override
protected int doPrepare(long positionUs) {
boolean sourcePrepared = source.prepare(positionUs);
if (!sourcePrepared) {
return TrackRenderer.STATE_UNPREPARED;
}
int trackCount = source.getTrackCount();
for (int i = 0; i < trackCount; i++) {
if (eia608Parser.canParse(source.getTrackInfo(i).mimeType)) {
trackIndex = i;
return TrackRenderer.STATE_PREPARED;
}
}
return TrackRenderer.STATE_IGNORE;
protected boolean handlesTrack(TrackInfo trackInfo) {
return eia608Parser.canParse(trackInfo.mimeType);
}
@Override
protected void onEnabled(long positionUs, boolean joining) {
source.enable(trackIndex, positionUs);
protected void onEnabled(long positionUs, boolean joining) throws ExoPlaybackException {
super.onEnabled(positionUs, joining);
seekToInternal();
}
@Override
protected void seekTo(long positionUs) throws ExoPlaybackException {
source.seekToUs(positionUs);
super.seekTo(positionUs);
seekToInternal();
}
@ -130,15 +116,14 @@ public final class Eia608TrackRenderer extends TrackRenderer implements Callback
@Override
protected void doSomeWork(long positionUs, long elapsedRealtimeUs) throws ExoPlaybackException {
source.continueBuffering(trackIndex, positionUs);
continueBufferingSource(positionUs);
if (isSamplePending()) {
maybeParsePendingSample(positionUs);
}
int result = inputStreamEnded ? SampleSource.END_OF_STREAM : SampleSource.SAMPLE_READ;
while (!isSamplePending() && result == SampleSource.SAMPLE_READ) {
result = source.readData(trackIndex, positionUs, formatHolder, sampleHolder, false);
result = readSource(positionUs, formatHolder, sampleHolder, false);
if (result == SampleSource.SAMPLE_READ) {
maybeParsePendingSample(positionUs);
} else if (result == SampleSource.END_OF_STREAM) {
@ -161,25 +146,6 @@ public final class Eia608TrackRenderer extends TrackRenderer implements Callback
}
}
@Override
protected void onDisabled() {
source.disable(trackIndex);
}
@Override
protected void maybeThrowError() throws ExoPlaybackException {
try {
source.maybeThrowError();
} catch (IOException e) {
throw new ExoPlaybackException(e);
}
}
@Override
protected long getDurationUs() {
return source.getTrackInfo(trackIndex).durationUs;
}
@Override
protected long getBufferedPositionUs() {
return TrackRenderer.END_OF_TRACK_US;