// Copyright 2016 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package org.chromium.chrome.browser.media.remote;
import android.os.SystemClock;
/**
* Class for extrapolating current playback position. The class occasionally receives updated
* playback position information from the MediaRoute, and extrapolates the current playback
* position.
*/
public class PositionExtrapolator {
private static final String TAG = "MediaFling";
private long mDuration;
private long mLastKnownPosition;
private long mTimestamp;
private boolean mIsPlaying;
public PositionExtrapolator() {
mDuration = 0;
mLastKnownPosition = 0;
mTimestamp = 0;
mIsPlaying = false;
}
/**
* Clears the duration and timestamp.
*/
public void clear() {
// Note: do not clear the stream position, since this is still needed so
// that we can reset the local stream position to match.
mDuration = 0;
mTimestamp = 0;
}
/**
* Update the extrapolator with the latest position info.
* @param duration The new duration.
* @param position The new playback position.
* @param timestamp The time stamp of this info, must be directly or indirectly aquired via
* {@link SystemClock.elapsedRealtime()}. The time stamp from the Cast receiver uses
* elapsedRealtime, so it can be used here. Don't use {@link SystemClock.uptimeMillis()} since
* it doesn't include the device sleep time.
*/
public void onPositionInfoUpdated(
long duration, long position, long timestamp) {
mDuration = Math.max(duration, 0);
mLastKnownPosition = Math.min(mDuration, Math.max(position, 0));
mTimestamp = timestamp;
}
/**
* Must be called whenever the remote playback is paused. If the playback
* state changes from playing to paused, the last known stream position will be updated by the
* extrapolated value.
*/
public void onPaused() {
if (!mIsPlaying) return;
long elapsedTime = SystemClock.elapsedRealtime();
onPositionInfoUpdated(mDuration, getPositionWithElapsedTime(elapsedTime), elapsedTime);
mIsPlaying = false;
}
/**
* Must be called whenever the remote playback resumes. If the playback state changes from
* paused to playing, the timestamp will be updated by the current {@link
* SystemClock.elapsedRealtime()}.
*/
public void onResumed() {
if (mIsPlaying) return;
onPositionInfoUpdated(mDuration, mLastKnownPosition, SystemClock.elapsedRealtime());
mIsPlaying = true;
}
/**
* Must be called whenever the remote playback is finished. The last known position will be
* updated by the duration.
*/
public void onFinished() {
onPositionInfoUpdated(mDuration, mDuration, SystemClock.elapsedRealtime());
mIsPlaying = false;
}
/**
* Must be called whenever the remote playback is seeking. The last known position will be
* updated by the position to be seeked to, and the extrapolator will assume the playback is
* paused during seeking.
* @param position The position to seek to.
*/
public void onSeek(long position) {
onPositionInfoUpdated(mDuration, position, SystemClock.elapsedRealtime());
mIsPlaying = false;
}
/**
* @return The current playback position.
*/
public long getPosition() {
return getPositionWithElapsedTime(SystemClock.elapsedRealtime());
}
/**
* @return The duration of the remote media.
*/
public long getDuration() {
return mDuration;
}
private long getPositionWithElapsedTime(long elapsedTime) {
if ((mTimestamp == 0) || !mIsPlaying || mLastKnownPosition >= mDuration) {
return mLastKnownPosition;
}
return Math.min(mLastKnownPosition + (elapsedTime - mTimestamp), mDuration);
}
}