Playback speed and trick mode control
To test this feature and view the example code, please see the (5.33.x) Android SDK 5 Example Code Quick Start guide.
The CONNECT Player SDK allows the playback speed to be adjusted while a stream is playing, with behaviour that varies depending on the chosen speed:
0.1 <= speed <= 8.0
The content will play at this speed, i.e. a multiple of the regular playback speed.speed <= -1.0; speed > 8.0
The player will enter trick mode.-1.0 < speed <= 0
Invalid - the player will ignore this value.
Any value set using this API - except 1.0 - will stop the player from attempting to catch up to the live edge on low latency content. The minimum and maximum playback speeds set in player configuration will be disregarded until this API is used to return the playback speed to 1.0.
Trick mode
The player will enter trick mode when the playback speed is set to a value above 8.0 or -1.0 or below. If the stream contains i-frame video tracks, the player will select the i-frame video track whose resolution is closest to the currently playing regular video track. I-frame tracks differ from regular video tracks in that they are typically around one frame per second, enabling faster seeking to implement fast-forward and rewind in an application.
The application must provide its own implementation of trick play fast-forward and rewind, for example, by defining how the playback speed affects the rate at which the application skips through the stream and at what interval seek operations are performed.
Example code
The playback-speed example code provides a basic implementation of trick play.
Enabling and disabling trick play
As specified by the SDK, playback speeds above 0 and less than or equal to 8 will speed up or slow down playback (with audio) accordingly, and the example code’s trick play mode will be disabled.
Trick play will be enabled if the speed is -1 or below or above 8.
private void setPlaybackSpeed(float speed) {
if (speed > -1.0f && speed <= 0f) {
OTVLog.w(TAG, "Invalid playback speed: " + speed + ". Continuing to play at speed: " + mPlaybackSpeed);
} else {
mPlaybackSpeed = speed;
mOTVVideoView.setPlaybackSpeed(mPlaybackSpeed);
if (speed > 0f && speed <= 8.0f) {
setTrickPlay(false);
} else if (speed <= -1.0f || speed > 8.0f) {
setTrickPlay(true);
}
}
// ...
}
Regardless of whether trick play is turned on or off when the playback speed is set, the Timer
object will be cleaned up and the existing OnSeekCompleteListener
removed from the OTVVideoView
.
If seek mode is enabled, the content will be paused (so there will be no audio), and the OnSeekCompleteListener
will be added to the OTVVideoView
before the first seek, which will be performed immediately. Subsequent seeks will be performed after a delay (see the final code snippet) to prevent particularly high-speed connections - allied with fast hardware - skipping too quickly through the content.
If seek mode is disabled, the content will be resumed.
If the application does not pause the content when the SDK selects a trick mode track, playback will continue with the trick mode track using the previously set playback speed (between 0.1 and 8). This means an extremely low frame-rate video track will be presented alongside audio, providing a poor user experience.
private Timer mTrickModeTimer;
private void setTrickPlay(boolean enable) {
if (mTrickModeTimer != null) {
mTrickModeTimer.cancel();
mTrickModeTimer.purge();
}
mOTVVideoView.removeEventListener(OTVVideoView.OTV_EVENT_TYPE_SEEK_COMPLETE, mTrickModeSeekCompleteListener);
if (enable) {
mOTVVideoView.pause();
mOTVVideoView.setOnSeekCompleteListener(mTrickModeSeekCompleteListener);
doSeek();
} else {
mOTVVideoView.resume();
}
}
Trick play implementation using TimerTask
While trick play is enabled, the content is paused and seeks are performed at one-second intervals to simulate fast-forward and rewind. The playback speed determines the seek increment. For example:
Speed -1: Seek backwards at a rate of one second per seek.
Speed 10: Seek forwards at a rate of ten seconds per seek.
To keep trick play smooth and give the impression of regularly-spaced seeks, the time of each seek operation is measured and then subtracted from the delay applied before the next seek is started. For example, if the initial seek takes 250ms, the subsequent seek will be delayed by only 750ms instead of 1000ms.
There is a limitation where seeks taking over 1000ms will cause the next seek to be performed immediately as a negative delay is not possible. It may be worth considering employing other strategies to compensate for prolonged seek times, such as increasing the increment of the next seek. For example, if a seek takes 2500ms at a playback speed of 10 (10,000ms per seek), the next could be increased to 12,500ms from the current position.
private static final int SEEK_DELAY = 1000;
private float mPlaybackSpeed = 1.0f;
// On completion of a seek, schedule the next seek with a delay.
// The delay will be reduced to account for the time taken on the previous seek
private final MediaPlayer.OnSeekCompleteListener mTrickModeSeekCompleteListener = mp -> {
SeekTimer.seekComplete();
mTrickModeTimer = new Timer();
mTrickModeTimer.schedule(new TimerTask() {
@Override
public void run() {
doSeek();
}
}, Math.max(0, SEEK_DELAY - SeekTimer.getLastSeekDuration()));
};
private void doSeek() {
SeekTimer.seekStart();
long seekPositionMs = mOTVVideoView.getCurrentPosition() + (long) (1000 * mPlaybackSpeed);
long[] range = mOTVVideoView.getSeekableRangeInfo();
if (seekPositionMs < range[0]) {
mOTVVideoView.seekTo((int)range[0]);
setPlaybackSpeed(1.0f);
} else if (seekPositionMs > range[1]) {
// Avoid restarting VOD streams by seeking 1ms before the end
mOTVVideoView.seekTo((int)range[1] - 1);
setPlaybackSpeed(1.0f);
} else {
mOTVVideoView.seekTo((int)seekPositionMs);
}
}