/*
* 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.ExoPlayer.ExoPlayerComponent;
import com.google.android.exoplayer.util.Assertions;
/**
* Renders a single component of media.
*
* <p>Internally, a renderer's lifecycle is managed by the owning {@link ExoPlayer}. The player
* will transition its renderers through various states as the overall playback state changes. The
* valid state transitions are shown below, annotated with the methods that are invoked during each
* transition.
* <p align="center"><img src="../../../../../doc_src/images/trackrenderer_state.png"
* alt="TrackRenderer state transitions"
* border="0"/></p>
*/
public abstract class TrackRenderer implements ExoPlayerComponent {
/**
* The renderer has been released and should not be used.
*/
protected static final int STATE_RELEASED = -2;
/**
* The renderer should be ignored by the player.
*/
protected static final int STATE_IGNORE = -1;
/**
* The renderer has not yet been prepared.
*/
protected static final int STATE_UNPREPARED = 0;
/**
* The renderer has completed necessary preparation. Preparation may include, for example,
* reading the header of a media file to determine the track format and duration.
* <p>
* The renderer should not hold scarce or expensive system resources (e.g. media decoders) and
* should not be actively buffering media data when in this state.
*/
protected static final int STATE_PREPARED = 1;
/**
* The renderer is enabled. It should either be ready to be started, or be actively working
* towards this state (e.g. a renderer in this state will typically hold any resources that it
* requires, such as media decoders, and will have buffered or be buffering any media data that
* is required to start playback).
*/
protected static final int STATE_ENABLED = 2;
/**
* The renderer is started. Calls to {@link #doSomeWork(long)} should cause the media to be
* rendered.
*/
protected static final int STATE_STARTED = 3;
/**
* Represents an unknown time or duration.
*/
public static final long UNKNOWN_TIME_US = -1;
/**
* Represents a time or duration that should match the duration of the longest track whose
* duration is known.
*/
public static final long MATCH_LONGEST_US = -2;
/**
* Represents the time of the end of the track.
*/
public static final long END_OF_TRACK_US = -3;
private int state;
/**
* A time source renderer is a renderer that, when started, advances its own playback position.
* This means that {@link #getCurrentPositionUs()} will return increasing positions independently
* to increasing values being passed to {@link #doSomeWork(long)}. A player may have at most one
* time source renderer. If provided, the player will use such a renderer as its source of time
* during playback.
* <p>
* This method may be called when the renderer is in any state.
*
* @return True if the renderer should be considered a time source. False otherwise.
*/
protected boolean isTimeSource() {
return false;
}
/**
* Returns the current state of the renderer.
*
* @return The current state (one of the STATE_* constants).
*/
protected final int getState() {
return state;
}
/**
* Prepares the renderer. This method is non-blocking, and hence it may be necessary to call it
* more than once in order to transition the renderer into the prepared state.
*
* @return The current state (one of the STATE_* constants), for convenience.
*/
/* package */ final int prepare() throws ExoPlaybackException {
Assertions.checkState(state == TrackRenderer.STATE_UNPREPARED);
state = doPrepare();
Assertions.checkState(state == TrackRenderer.STATE_UNPREPARED ||
state == TrackRenderer.STATE_PREPARED ||
state == TrackRenderer.STATE_IGNORE);
return state;
}
/**
* Invoked to make progress when the renderer is in the {@link #STATE_UNPREPARED} state. This
* method will be called repeatedly until a value other than {@link #STATE_UNPREPARED} is
* returned.
* <p>
* This method should return quickly, and should not block if the renderer is currently unable to
* make any useful progress.
*
* @return The new state of the renderer. One of {@link #STATE_UNPREPARED},
* {@link #STATE_PREPARED} and {@link #STATE_IGNORE}.
* @throws ExoPlaybackException If an error occurs.
*/
protected abstract int doPrepare() throws ExoPlaybackException;
/**
* Enable the renderer.
*
* @param timeUs The player's current position.
* @param joining Whether this renderer is being enabled to join an ongoing playback. If true
* then {@link #start} must be called immediately after this method returns (unless a
* {@link ExoPlaybackException} is thrown).
*/
/* package */ final void enable(long timeUs, boolean joining) throws ExoPlaybackException {
Assertions.checkState(state == TrackRenderer.STATE_PREPARED);
state = TrackRenderer.STATE_ENABLED;
onEnabled(timeUs, joining);
}
/**
* Called when the renderer is enabled.
* <p>
* The default implementation is a no-op.
*
* @param timeUs The player's current position.
* @param joining Whether this renderer is being enabled to join an ongoing playback. If true
* then {@link #onStarted} is guaranteed to be called immediately after this method returns
* (unless a {@link ExoPlaybackException} is thrown).
* @throws ExoPlaybackException If an error occurs.
*/
protected void onEnabled(long timeUs, boolean joining) throws ExoPlaybackException {
// Do nothing.
}
/**
* Starts the renderer, meaning that calls to {@link #doSomeWork(long)} will cause the
* track to be rendered.
*/
/* package */ final void start() throws ExoPlaybackException {
Assertions.checkState(state == TrackRenderer.STATE_ENABLED);
state = TrackRenderer.STATE_STARTED;
onStarted();
}
/**
* Called when the renderer is started.
* <p>
* The default implementation is a no-op.
*
* @throws ExoPlaybackException If an error occurs.
*/
protected void onStarted() throws ExoPlaybackException {
// Do nothing.
}
/**
* Stops the renderer.
*/
/* package */ final void stop() throws ExoPlaybackException {
Assertions.checkState(state == TrackRenderer.STATE_STARTED);
state = TrackRenderer.STATE_ENABLED;
onStopped();
}
/**
* Called when the renderer is stopped.
* <p>
* The default implementation is a no-op.
*
* @throws ExoPlaybackException If an error occurs.
*/
protected void onStopped() throws ExoPlaybackException {
// Do nothing.
}
/**
* Disable the renderer.
*/
/* package */ final void disable() throws ExoPlaybackException {
Assertions.checkState(state == TrackRenderer.STATE_ENABLED);
state = TrackRenderer.STATE_PREPARED;
onDisabled();
}
/**
* Called when the renderer is disabled.
* <p>
* The default implementation is a no-op.
*
* @throws ExoPlaybackException If an error occurs.
*/
protected void onDisabled() throws ExoPlaybackException {
// Do nothing.
}
/**
* Releases the renderer.
*/
/* package */ final void release() throws ExoPlaybackException {
Assertions.checkState(state != TrackRenderer.STATE_ENABLED
&& state != TrackRenderer.STATE_STARTED
&& state != TrackRenderer.STATE_RELEASED);
state = TrackRenderer.STATE_RELEASED;
onReleased();
}
/**
* Called when the renderer is released.
* <p>
* The default implementation is a no-op.
*
* @throws ExoPlaybackException If an error occurs.
*/
protected void onReleased() throws ExoPlaybackException {
// Do nothing.
}
/**
* Whether the renderer is ready for the {@link ExoPlayer} instance to transition to
* {@link ExoPlayer#STATE_ENDED}. The player will make this transition as soon as {@code true} is
* returned by all of its {@link TrackRenderer}s.
* <p>
* This method may be called when the renderer is in the following states:
* {@link #STATE_ENABLED}, {@link #STATE_STARTED}
*
* @return Whether the renderer is ready for the player to transition to the ended state.
*/
protected abstract boolean isEnded();
/**
* Whether the renderer is able to immediately render media from the current position.
* <p>
* If the renderer is in the {@link #STATE_STARTED} state then returning true indicates that the
* renderer has everything that it needs to continue playback. Returning false indicates that
* the player should pause until the renderer is ready.
* <p>
* If the renderer is in the {@link #STATE_ENABLED} state then returning true indicates that the
* renderer is ready for playback to be started. Returning false indicates that it is not.
* <p>
* This method may be called when the renderer is in the following states:
* {@link #STATE_ENABLED}, {@link #STATE_STARTED}
*
* @return True if the renderer is ready to render media. False otherwise.
*/
protected abstract boolean isReady();
/**
* Invoked to make progress when the renderer is in the {@link #STATE_ENABLED} or
* {@link #STATE_STARTED} states.
* <p>
* If the renderer's state is {@link #STATE_STARTED}, then repeated calls to this method should
* cause the media track to be rendered. If the state is {@link #STATE_ENABLED}, then repeated
* calls should make progress towards getting the renderer into a position where it is ready to
* render the track.
* <p>
* This method should return quickly, and should not block if the renderer is currently unable to
* make any useful progress.
* <p>
* This method may be called when the renderer is in the following states:
* {@link #STATE_ENABLED}, {@link #STATE_STARTED}
*
* @param timeUs The current playback time.
* @throws ExoPlaybackException If an error occurs.
*/
protected abstract void doSomeWork(long timeUs) throws ExoPlaybackException;
/**
* Returns the duration of the media being rendered.
* <p>
* This method may be called when the renderer is in the following states:
* {@link #STATE_PREPARED}, {@link #STATE_ENABLED}, {@link #STATE_STARTED}
*
* @return The duration of the track in micro-seconds, or {@link #MATCH_LONGEST_US} if
* the track's duration should match that of the longest track whose duration is known, or
* or {@link #UNKNOWN_TIME_US} if the duration is not known.
*/
protected abstract long getDurationUs();
/**
* Returns the current playback position.
* <p>
* This method may be called when the renderer is in the following states:
* {@link #STATE_ENABLED}, {@link #STATE_STARTED}
*
* @return The current playback position in micro-seconds.
*/
protected abstract long getCurrentPositionUs();
/**
* Returns an estimate of the absolute position in micro-seconds up to which data is buffered.
* <p>
* This method may be called when the renderer is in the following states:
* {@link #STATE_ENABLED}, {@link #STATE_STARTED}
*
* @return An estimate of the absolute position in micro-seconds up to which data is buffered,
* or {@link #END_OF_TRACK_US} if the track is fully buffered, or {@link #UNKNOWN_TIME_US} if
* no estimate is available.
*/
protected abstract long getBufferedPositionUs();
/**
* Seeks to a specified time in the track.
* <p>
* This method may be called when the renderer is in the following states:
* {@link #STATE_ENABLED}
*
* @param timeUs The desired time in micro-seconds.
* @throws ExoPlaybackException If an error occurs.
*/
protected abstract void seekTo(long timeUs) throws ExoPlaybackException;
public void setPlaybackSpeed(float speed) {
// Do nothing here, audio track renderer should override this method to handle speed.
}
@Override
public void handleMessage(int what, Object object) throws ExoPlaybackException {
// Do nothing.
}
}