FileDocCategorySizeDatePackage
VariableSpeed.javaAPI DocAndroid 5.1 API14428Thu Mar 12 22:22:48 GMT 2015com.android.ex.variablespeed

VariableSpeed

public class VariableSpeed extends Object implements MediaPlayerProxy
This class behaves in a similar fashion to the MediaPlayer, but by using native code it is able to use variable-speed playback.

This class is thread-safe. It's not yet perfect though, see the unit tests for details - there is insufficient testing for the concurrent logic. You are probably best advised to use thread confinment until the unit tests are more complete with regards to threading.

The easiest way to ensure that calls to this class are not made concurrently (besides only ever accessing it from one thread) is to wrap it in a {@link SingleThreadedMediaPlayerProxy}, designed just for this purpose.

Fields Summary
private static final String
TAG
private final Executor
mExecutor
private final Object
lock
private MediaPlayerDataSource
mDataSource
private boolean
mIsPrepared
private boolean
mHasDuration
private boolean
mHasStartedPlayback
private CountDownLatch
mEngineInitializedLatch
private CountDownLatch
mPlaybackFinishedLatch
private boolean
mHasBeenReleased
private boolean
mIsReadyToReUse
private boolean
mSkipCompletionReport
private int
mStartPosition
private float
mCurrentPlaybackRate
private int
mDuration
private MediaPlayer.OnCompletionListener
mCompletionListener
private int
mAudioStreamType
Constructors Summary
private VariableSpeed(Executor executor)

        Preconditions.checkNotNull(executor);
        mExecutor = executor;
        try {
            VariableSpeedNative.loadLibrary();
        } catch (UnsatisfiedLinkError e) {
            throw new UnsupportedOperationException("could not load library", e);
        } catch (SecurityException e) {
            throw new UnsupportedOperationException("could not load library", e);
        }
        reset();
    
Methods Summary
private voidcheck(boolean condition, java.lang.String exception)

        if (!condition) {
            throw new IllegalStateException(exception);
        }
    
private voidcheckNotNull(java.lang.Object argument, java.lang.String argumentName)

        if (argument == null) {
            throw new IllegalArgumentException(argumentName + " must not be null");
        }
    
public static MediaPlayerProxycreateVariableSpeed(java.util.concurrent.Executor executor)

        return new SingleThreadedMediaPlayerProxy(new VariableSpeed(executor));
    
public intgetCurrentPosition()

        synchronized (lock) {
            check(!mHasBeenReleased, "has been released, reset before use");
            if (!mHasStartedPlayback) {
                return 0;
            }
            if (!hasEngineBeenInitialized()) {
                return 0;
            }
            if (!hasPlaybackFinished()) {
                return VariableSpeedNative.getCurrentPosition();
            }
            return mDuration;
        }
    
public intgetDuration()

        synchronized (lock) {
            check(!mHasBeenReleased, "has been released, reset before use");
            check(mHasDuration, "you haven't called prepare, can't get the duration");
            return mDuration;
        }
    
private booleanhasEngineBeenInitialized()

        return mEngineInitializedLatch.getCount() <= 0;
    
private booleanhasPlaybackFinished()

        return mPlaybackFinishedLatch.getCount() <= 0;
    
private voidinnerSetDataSource(MediaPlayerDataSource source)

        checkNotNull(source, "source");
        synchronized (lock) {
            check(!mHasBeenReleased, "has been released, reset before use");
            check(mDataSource == null, "cannot setDataSource more than once");
            mDataSource = source;
        }
    
public booleanisPlaying()

        synchronized (lock) {
            return isReadyToPlay() && mHasStartedPlayback && !hasPlaybackFinished();
        }
    
public booleanisReadyToPlay()

        synchronized (lock) {
            return !mHasBeenReleased && mHasDuration;
        }
    
public voidpause()

        synchronized (lock) {
            check(!mHasBeenReleased, "has been released, reset before use");
        }
        stopCurrentPlayback();
    
public voidprepare()

        MediaPlayerDataSource dataSource;
        int audioStreamType;
        synchronized (lock) {
            check(!mHasBeenReleased, "has been released, reset before use");
            check(mDataSource != null, "must setDataSource before you prepare");
            check(!mIsPrepared, "cannot prepare more than once");
            mIsPrepared = true;
            dataSource = mDataSource;
            audioStreamType = mAudioStreamType;
        }
        // NYI This should become another executable that we can wait on.
        MediaPlayer mediaPlayer = new MediaPlayer();
        mediaPlayer.setAudioStreamType(audioStreamType);
        dataSource.setAsSourceFor(mediaPlayer);
        mediaPlayer.prepare();
        synchronized (lock) {
            check(!mHasDuration, "can't have duration, this is impossible");
            mHasDuration = true;
            mDuration = mediaPlayer.getDuration();
        }
        mediaPlayer.release();
    
public voidrelease()

        synchronized (lock) {
            if (mHasBeenReleased) {
                return;
            }
            mHasBeenReleased = true;
        }
        stopCurrentPlayback();
        boolean requiresShutdown = false;
        synchronized (lock) {
            requiresShutdown = hasEngineBeenInitialized();
        }
        if (requiresShutdown) {
            VariableSpeedNative.shutdownEngine();
        }
        synchronized (lock) {
            mIsReadyToReUse = true;
        }
    
private voidreportException(java.lang.Exception e)

        Log.e(TAG, "playback error:", e);
    
public voidreset()

        boolean requiresRelease;
        synchronized (lock) {
            requiresRelease = !mHasBeenReleased;
        }
        if (requiresRelease) {
            release();
        }
        synchronized (lock) {
            check(mHasBeenReleased && mIsReadyToReUse, "to re-use, must call reset after release");
            mDataSource = null;
            mIsPrepared = false;
            mHasDuration = false;
            mHasStartedPlayback = false;
            mEngineInitializedLatch = new CountDownLatch(1);
            mPlaybackFinishedLatch = new CountDownLatch(1);
            mHasBeenReleased = false;
            mIsReadyToReUse = false;
            mSkipCompletionReport = false;
            mStartPosition = 0;
            mDuration = 0;
        }
    
public voidseekTo(int startPosition)

        boolean currentlyPlaying;
        MediaPlayerDataSource dataSource;
        synchronized (lock) {
            check(!mHasBeenReleased, "has been released, reset before use");
            check(mHasDuration, "you can't seek until you have prepared");
            currentlyPlaying = mHasStartedPlayback && !hasPlaybackFinished();
            mStartPosition = Math.min(startPosition, mDuration);
            dataSource = mDataSource;
        }
        if (currentlyPlaying) {
            stopAndStartPlayingAgain(dataSource);
        }
    
public voidsetAudioStreamType(int audioStreamType)

        synchronized (lock) {
            mAudioStreamType = audioStreamType;
        }
    
public voidsetDataSource(android.content.Context context, android.net.Uri intentUri)

        checkNotNull(context, "context");
        checkNotNull(intentUri, "intentUri");
        innerSetDataSource(new MediaPlayerDataSource(context, intentUri));
    
public voidsetDataSource(java.lang.String path)

        checkNotNull(path, "path");
        innerSetDataSource(new MediaPlayerDataSource(path));
    
public voidsetOnCompletionListener(MediaPlayer.OnCompletionListener listener)

        synchronized (lock) {
            check(!mHasBeenReleased, "has been released, reset before use");
            mCompletionListener = listener;
        }
    
public voidsetOnErrorListener(MediaPlayer.OnErrorListener listener)

        synchronized (lock) {
            check(!mHasBeenReleased, "has been released, reset before use");
            // TODO: I haven't actually added any error listener code.
        }
    
public voidsetVariableSpeed(float rate)

        // TODO: are there situations in which the engine has been destroyed, so
        // that this will segfault?
        synchronized (lock) {
            check(!mHasBeenReleased, "has been released, reset before use");
            // TODO: This too is wrong, once we've started preparing the variable speed set
            // will not be enough.
            if (mHasStartedPlayback) {
                VariableSpeedNative.setVariableSpeed(rate);
            }
            mCurrentPlaybackRate = rate;
        }
    
public voidstart()

        MediaPlayerDataSource restartWithThisDataSource = null;
        synchronized (lock) {
            check(!mHasBeenReleased, "has been released, reset before use");
            check(mIsPrepared, "must have prepared before you can start");
            if (!mHasStartedPlayback) {
                // Playback has not started. Start it.
                mHasStartedPlayback = true;
                EngineParameters engineParameters = new EngineParameters.Builder()
                        .initialRate(mCurrentPlaybackRate)
                        .startPositionMillis(mStartPosition)
                        .audioStreamType(mAudioStreamType)
                        .build();
                VariableSpeedNative.initializeEngine(engineParameters);
                VariableSpeedNative.startPlayback();
                mEngineInitializedLatch.countDown();
                mExecutor.execute(new PlaybackRunnable(mDataSource));
            } else {
                // Playback has already started. Restart it, without holding the
                // lock.
                restartWithThisDataSource = mDataSource;
            }
        }
        if (restartWithThisDataSource != null) {
            stopAndStartPlayingAgain(restartWithThisDataSource);
        }
    
private voidstopAndStartPlayingAgain(MediaPlayerDataSource source)

        stopCurrentPlayback();
        reset();
        innerSetDataSource(source);
        try {
            prepare();
        } catch (IOException e) {
            reportException(e);
            return;
        }
        start();
        return;
    
private voidstopCurrentPlayback()
Stops the current playback, returns once it has stopped.

        boolean isPlaying;
        CountDownLatch engineInitializedLatch;
        CountDownLatch playbackFinishedLatch;
        synchronized (lock) {
            isPlaying = mHasStartedPlayback && !hasPlaybackFinished();
            engineInitializedLatch = mEngineInitializedLatch;
            playbackFinishedLatch = mPlaybackFinishedLatch;
            if (isPlaying) {
                mSkipCompletionReport = true;
            }
        }
        if (isPlaying) {
            waitForLatch(engineInitializedLatch);
            VariableSpeedNative.stopPlayback();
            waitForLatch(playbackFinishedLatch);
        }
    
private voidwaitForLatch(java.util.concurrent.CountDownLatch latch)

        try {
            boolean success = latch.await(1, TimeUnit.SECONDS);
            if (!success) {
                reportException(new TimeoutException("waited too long"));
            }
        } catch (InterruptedException e) {
            // Preserve the interrupt status, though this is unexpected.
            Thread.currentThread().interrupt();
            reportException(e);
        }