Commit 0e6ef0ed by andrewlewis Committed by Oliver Woodman

Add Sonic library for audio speed adjustment.

Add methods to ExoPlayer for setting/getting the playback speed, using
SonicAudioProcessor.

Remove PlaybackParams support, as the AudioTrack timestamp does not work
reliably on Marshmallow. The platform also uses Sonic and performance
should be comparable between the Java and native versions on recent Android
runtimes.

In a later change, SonicAudioProcessor will be made public so it can
be used in conjunction with other processors.

Issue: #26

-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=151027121
parent 6faf5663
Showing with 587 additions and 84 deletions
...@@ -22,6 +22,7 @@ import com.google.android.exoplayer2.C; ...@@ -22,6 +22,7 @@ import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.ExoPlaybackException; import com.google.android.exoplayer2.ExoPlaybackException;
import com.google.android.exoplayer2.ExoPlayer; import com.google.android.exoplayer2.ExoPlayer;
import com.google.android.exoplayer2.Format; import com.google.android.exoplayer2.Format;
import com.google.android.exoplayer2.PlaybackParameters;
import com.google.android.exoplayer2.RendererCapabilities; import com.google.android.exoplayer2.RendererCapabilities;
import com.google.android.exoplayer2.Timeline; import com.google.android.exoplayer2.Timeline;
import com.google.android.exoplayer2.audio.AudioRendererEventListener; import com.google.android.exoplayer2.audio.AudioRendererEventListener;
...@@ -100,6 +101,12 @@ import java.util.Locale; ...@@ -100,6 +101,12 @@ import java.util.Locale;
} }
@Override @Override
public void onPlaybackParametersChanged(PlaybackParameters playbackParameters) {
Log.d(TAG, "playbackParameters " + String.format(
"[speed=%.2f, pitch=%.2f]", playbackParameters.speed, playbackParameters.pitch));
}
@Override
public void onTimelineChanged(Timeline timeline, Object manifest) { public void onTimelineChanged(Timeline timeline, Object manifest) {
int periodCount = timeline.getPeriodCount(); int periodCount = timeline.getPeriodCount();
int windowCount = timeline.getWindowCount(); int windowCount = timeline.getWindowCount();
......
...@@ -34,6 +34,7 @@ import com.google.android.exoplayer2.DefaultLoadControl; ...@@ -34,6 +34,7 @@ import com.google.android.exoplayer2.DefaultLoadControl;
import com.google.android.exoplayer2.ExoPlaybackException; import com.google.android.exoplayer2.ExoPlaybackException;
import com.google.android.exoplayer2.ExoPlayer; import com.google.android.exoplayer2.ExoPlayer;
import com.google.android.exoplayer2.ExoPlayerFactory; import com.google.android.exoplayer2.ExoPlayerFactory;
import com.google.android.exoplayer2.PlaybackParameters;
import com.google.android.exoplayer2.SimpleExoPlayer; import com.google.android.exoplayer2.SimpleExoPlayer;
import com.google.android.exoplayer2.Timeline; import com.google.android.exoplayer2.Timeline;
import com.google.android.exoplayer2.drm.DefaultDrmSessionManager; import com.google.android.exoplayer2.drm.DefaultDrmSessionManager;
...@@ -428,6 +429,11 @@ public class PlayerActivity extends Activity implements OnClickListener, ExoPlay ...@@ -428,6 +429,11 @@ public class PlayerActivity extends Activity implements OnClickListener, ExoPlay
} }
@Override @Override
public void onPlaybackParametersChanged(PlaybackParameters playbackParameters) {
// Do nothing.
}
@Override
public void onTimelineChanged(Timeline timeline, Object manifest) { public void onTimelineChanged(Timeline timeline, Object manifest) {
// Do nothing. // Do nothing.
} }
......
...@@ -22,6 +22,7 @@ import android.test.InstrumentationTestCase; ...@@ -22,6 +22,7 @@ import android.test.InstrumentationTestCase;
import com.google.android.exoplayer2.ExoPlaybackException; import com.google.android.exoplayer2.ExoPlaybackException;
import com.google.android.exoplayer2.ExoPlayer; import com.google.android.exoplayer2.ExoPlayer;
import com.google.android.exoplayer2.ExoPlayerFactory; import com.google.android.exoplayer2.ExoPlayerFactory;
import com.google.android.exoplayer2.PlaybackParameters;
import com.google.android.exoplayer2.Renderer; import com.google.android.exoplayer2.Renderer;
import com.google.android.exoplayer2.Timeline; import com.google.android.exoplayer2.Timeline;
import com.google.android.exoplayer2.extractor.mkv.MatroskaExtractor; import com.google.android.exoplayer2.extractor.mkv.MatroskaExtractor;
...@@ -103,6 +104,11 @@ public class FlacPlaybackTest extends InstrumentationTestCase { ...@@ -103,6 +104,11 @@ public class FlacPlaybackTest extends InstrumentationTestCase {
} }
@Override @Override
public void onPlaybackParametersChanged(PlaybackParameters playbackParameters) {
// Do nothing.
}
@Override
public void onTimelineChanged(Timeline timeline, Object manifest) { public void onTimelineChanged(Timeline timeline, Object manifest) {
// Do nothing. // Do nothing.
} }
......
...@@ -22,6 +22,7 @@ import android.test.InstrumentationTestCase; ...@@ -22,6 +22,7 @@ import android.test.InstrumentationTestCase;
import com.google.android.exoplayer2.ExoPlaybackException; import com.google.android.exoplayer2.ExoPlaybackException;
import com.google.android.exoplayer2.ExoPlayer; import com.google.android.exoplayer2.ExoPlayer;
import com.google.android.exoplayer2.ExoPlayerFactory; import com.google.android.exoplayer2.ExoPlayerFactory;
import com.google.android.exoplayer2.PlaybackParameters;
import com.google.android.exoplayer2.Renderer; import com.google.android.exoplayer2.Renderer;
import com.google.android.exoplayer2.Timeline; import com.google.android.exoplayer2.Timeline;
import com.google.android.exoplayer2.extractor.mkv.MatroskaExtractor; import com.google.android.exoplayer2.extractor.mkv.MatroskaExtractor;
...@@ -103,6 +104,11 @@ public class OpusPlaybackTest extends InstrumentationTestCase { ...@@ -103,6 +104,11 @@ public class OpusPlaybackTest extends InstrumentationTestCase {
} }
@Override @Override
public void onPlaybackParametersChanged(PlaybackParameters playbackParameters) {
// Do nothing.
}
@Override
public void onTimelineChanged(Timeline timeline, Object manifest) { public void onTimelineChanged(Timeline timeline, Object manifest) {
// Do nothing. // Do nothing.
} }
......
...@@ -23,6 +23,7 @@ import android.util.Log; ...@@ -23,6 +23,7 @@ import android.util.Log;
import com.google.android.exoplayer2.ExoPlaybackException; import com.google.android.exoplayer2.ExoPlaybackException;
import com.google.android.exoplayer2.ExoPlayer; import com.google.android.exoplayer2.ExoPlayer;
import com.google.android.exoplayer2.ExoPlayerFactory; import com.google.android.exoplayer2.ExoPlayerFactory;
import com.google.android.exoplayer2.PlaybackParameters;
import com.google.android.exoplayer2.Renderer; import com.google.android.exoplayer2.Renderer;
import com.google.android.exoplayer2.Timeline; import com.google.android.exoplayer2.Timeline;
import com.google.android.exoplayer2.extractor.mkv.MatroskaExtractor; import com.google.android.exoplayer2.extractor.mkv.MatroskaExtractor;
...@@ -135,6 +136,11 @@ public class VpxPlaybackTest extends InstrumentationTestCase { ...@@ -135,6 +136,11 @@ public class VpxPlaybackTest extends InstrumentationTestCase {
} }
@Override @Override
public void onPlaybackParametersChanged(PlaybackParameters playbackParameters) {
// Do nothing.
}
@Override
public void onTimelineChanged(Timeline timeline, Object manifest) { public void onTimelineChanged(Timeline timeline, Object manifest) {
// Do nothing. // Do nothing.
} }
......
...@@ -141,6 +141,16 @@ public final class ExoPlayerTest extends TestCase { ...@@ -141,6 +141,16 @@ public final class ExoPlayerTest extends TestCase {
} }
@Override @Override
public PlaybackParameters setPlaybackParameters(PlaybackParameters playbackParameters) {
return PlaybackParameters.DEFAULT;
}
@Override
public PlaybackParameters getPlaybackParameters() {
return PlaybackParameters.DEFAULT;
}
@Override
public boolean isEnded() { public boolean isEnded() {
// Allow playback to end once the final period is playing. // Allow playback to end once the final period is playing.
return playerWrapper.positionDiscontinuityCount == 2; return playerWrapper.positionDiscontinuityCount == 2;
...@@ -272,6 +282,11 @@ public final class ExoPlayerTest extends TestCase { ...@@ -272,6 +282,11 @@ public final class ExoPlayerTest extends TestCase {
positionDiscontinuityCount++; positionDiscontinuityCount++;
} }
@Override
public void onPlaybackParametersChanged(PlaybackParameters playbackParameters) {
// Do nothing.
}
} }
private static final class TimelineWindowDefinition { private static final class TimelineWindowDefinition {
......
...@@ -482,15 +482,6 @@ public final class C { ...@@ -482,15 +482,6 @@ public final class C {
/** /**
* A type of a message that can be passed to an audio {@link Renderer} via * A type of a message that can be passed to an audio {@link Renderer} via
* {@link ExoPlayer#sendMessages} or {@link ExoPlayer#blockingSendMessages}. The message object * {@link ExoPlayer#sendMessages} or {@link ExoPlayer#blockingSendMessages}. The message object
* should be a {@link android.media.PlaybackParams}, or null, which will be used to configure the
* underlying {@link android.media.AudioTrack}. The message object should not be modified by the
* caller after it has been passed.
*/
public static final int MSG_SET_PLAYBACK_PARAMS = 3;
/**
* A type of a message that can be passed to an audio {@link Renderer} via
* {@link ExoPlayer#sendMessages} or {@link ExoPlayer#blockingSendMessages}. The message object
* should be one of the integer stream types in {@link C.StreamType}, and will specify the stream * should be one of the integer stream types in {@link C.StreamType}, and will specify the stream
* type of the underlying {@link android.media.AudioTrack}. See also * type of the underlying {@link android.media.AudioTrack}. See also
* {@link android.media.AudioTrack#AudioTrack(int, int, int, int, int, int)}. If the stream type * {@link android.media.AudioTrack#AudioTrack(int, int, int, int, int, int)}. If the stream type
...@@ -500,7 +491,7 @@ public final class C { ...@@ -500,7 +491,7 @@ public final class C {
* introduce a brief gap in audio output. Note also that tracks in the same audio session must * introduce a brief gap in audio output. Note also that tracks in the same audio session must
* share the same routing, so a new audio session id will be generated. * share the same routing, so a new audio session id will be generated.
*/ */
public static final int MSG_SET_STREAM_TYPE = 4; public static final int MSG_SET_STREAM_TYPE = 3;
/** /**
* The type of a message that can be passed to a {@link MediaCodec}-based video {@link Renderer} * The type of a message that can be passed to a {@link MediaCodec}-based video {@link Renderer}
...@@ -510,7 +501,7 @@ public final class C { ...@@ -510,7 +501,7 @@ public final class C {
* Note that the scaling mode only applies if the {@link Surface} targeted by the renderer is * Note that the scaling mode only applies if the {@link Surface} targeted by the renderer is
* owned by a {@link android.view.SurfaceView}. * owned by a {@link android.view.SurfaceView}.
*/ */
public static final int MSG_SET_SCALING_MODE = 5; public static final int MSG_SET_SCALING_MODE = 4;
/** /**
* Applications or extensions may define custom {@code MSG_*} constants greater than or equal to * Applications or extensions may define custom {@code MSG_*} constants greater than or equal to
......
...@@ -15,6 +15,7 @@ ...@@ -15,6 +15,7 @@
*/ */
package com.google.android.exoplayer2; package com.google.android.exoplayer2;
import android.support.annotation.Nullable;
import com.google.android.exoplayer2.audio.MediaCodecAudioRenderer; import com.google.android.exoplayer2.audio.MediaCodecAudioRenderer;
import com.google.android.exoplayer2.metadata.MetadataRenderer; import com.google.android.exoplayer2.metadata.MetadataRenderer;
import com.google.android.exoplayer2.source.ConcatenatingMediaSource; import com.google.android.exoplayer2.source.ConcatenatingMediaSource;
...@@ -168,6 +169,16 @@ public interface ExoPlayer { ...@@ -168,6 +169,16 @@ public interface ExoPlayer {
*/ */
void onPositionDiscontinuity(); void onPositionDiscontinuity();
/**
* Called when the current playback parameters change. The playback parameters may change due to
* a call to {@link ExoPlayer#setPlaybackParameters(PlaybackParameters)}, or the player itself
* may change them (for example, if audio playback switches to passthrough mode, where speed
* adjustment is no longer possible).
*
* @param playbackParameters The playback parameters.
*/
void onPlaybackParametersChanged(PlaybackParameters playbackParameters);
} }
/** /**
...@@ -341,6 +352,28 @@ public interface ExoPlayer { ...@@ -341,6 +352,28 @@ public interface ExoPlayer {
void seekTo(int windowIndex, long positionMs); void seekTo(int windowIndex, long positionMs);
/** /**
* Attempts to set the playback parameters. Passing {@code null} sets the parameters to the
* default, {@link PlaybackParameters#DEFAULT}, which means there is no speed or pitch adjustment.
* <p>
* Playback parameters changes may cause the player to buffer.
* {@link EventListener#onPlaybackParametersChanged(PlaybackParameters)} will be called whenever
* the currently active playback parameters change. When that listener is called, the parameters
* passed to it may not match {@code playbackParameters}. For example, the chosen speed or pitch
* may be out of range, in which case they are constrained to a set of permitted values. If it is
* not possible to change the playback parameters, the listener will not be invoked.
*
* @param playbackParameters The playback parameters, or {@code null} to use the defaults.
*/
void setPlaybackParameters(@Nullable PlaybackParameters playbackParameters);
/**
* Returns the currently active playback parameters.
*
* @see EventListener#onPlaybackParametersChanged(PlaybackParameters)
*/
PlaybackParameters getPlaybackParameters();
/**
* Stops playback. Use {@code setPlayWhenReady(false)} rather than this method if the intention * Stops playback. Use {@code setPlayWhenReady(false)} rather than this method if the intention
* is to pause playback. * is to pause playback.
* <p> * <p>
......
...@@ -19,6 +19,7 @@ import android.annotation.SuppressLint; ...@@ -19,6 +19,7 @@ import android.annotation.SuppressLint;
import android.os.Handler; import android.os.Handler;
import android.os.Looper; import android.os.Looper;
import android.os.Message; import android.os.Message;
import android.support.annotation.Nullable;
import android.util.Log; import android.util.Log;
import com.google.android.exoplayer2.ExoPlayerImplInternal.PlaybackInfo; import com.google.android.exoplayer2.ExoPlayerImplInternal.PlaybackInfo;
import com.google.android.exoplayer2.ExoPlayerImplInternal.SourceInfo; import com.google.android.exoplayer2.ExoPlayerImplInternal.SourceInfo;
...@@ -57,6 +58,7 @@ import java.util.concurrent.CopyOnWriteArraySet; ...@@ -57,6 +58,7 @@ import java.util.concurrent.CopyOnWriteArraySet;
private Object manifest; private Object manifest;
private TrackGroupArray trackGroups; private TrackGroupArray trackGroups;
private TrackSelectionArray trackSelections; private TrackSelectionArray trackSelections;
private PlaybackParameters playbackParameters;
// Playback information when there is no pending seek/set source operation. // Playback information when there is no pending seek/set source operation.
private PlaybackInfo playbackInfo; private PlaybackInfo playbackInfo;
...@@ -87,6 +89,7 @@ import java.util.concurrent.CopyOnWriteArraySet; ...@@ -87,6 +89,7 @@ import java.util.concurrent.CopyOnWriteArraySet;
period = new Timeline.Period(); period = new Timeline.Period();
trackGroups = TrackGroupArray.EMPTY; trackGroups = TrackGroupArray.EMPTY;
trackSelections = emptyTrackSelections; trackSelections = emptyTrackSelections;
playbackParameters = PlaybackParameters.DEFAULT;
eventHandler = new Handler() { eventHandler = new Handler() {
@Override @Override
public void handleMessage(Message msg) { public void handleMessage(Message msg) {
...@@ -197,6 +200,19 @@ import java.util.concurrent.CopyOnWriteArraySet; ...@@ -197,6 +200,19 @@ import java.util.concurrent.CopyOnWriteArraySet;
} }
@Override @Override
public void setPlaybackParameters(@Nullable PlaybackParameters playbackParameters) {
if (playbackParameters == null) {
playbackParameters = PlaybackParameters.DEFAULT;
}
internalPlayer.setPlaybackParameters(playbackParameters);
}
@Override
public PlaybackParameters getPlaybackParameters() {
return playbackParameters;
}
@Override
public void stop() { public void stop() {
internalPlayer.stop(); internalPlayer.stop();
} }
...@@ -376,6 +392,16 @@ import java.util.concurrent.CopyOnWriteArraySet; ...@@ -376,6 +392,16 @@ import java.util.concurrent.CopyOnWriteArraySet;
} }
break; break;
} }
case ExoPlayerImplInternal.MSG_PLAYBACK_PARAMETERS_CHANGED: {
PlaybackParameters playbackParameters = (PlaybackParameters) msg.obj;
if (!this.playbackParameters.equals(playbackParameters)) {
this.playbackParameters = playbackParameters;
for (EventListener listener : listeners) {
listener.onPlaybackParametersChanged(playbackParameters);
}
}
break;
}
case ExoPlayerImplInternal.MSG_ERROR: { case ExoPlayerImplInternal.MSG_ERROR: {
ExoPlaybackException exception = (ExoPlaybackException) msg.obj; ExoPlaybackException exception = (ExoPlaybackException) msg.obj;
for (EventListener listener : listeners) { for (EventListener listener : listeners) {
...@@ -383,6 +409,8 @@ import java.util.concurrent.CopyOnWriteArraySet; ...@@ -383,6 +409,8 @@ import java.util.concurrent.CopyOnWriteArraySet;
} }
break; break;
} }
default:
throw new IllegalStateException();
} }
} }
......
...@@ -96,20 +96,22 @@ import java.io.IOException; ...@@ -96,20 +96,22 @@ import java.io.IOException;
public static final int MSG_SEEK_ACK = 4; public static final int MSG_SEEK_ACK = 4;
public static final int MSG_POSITION_DISCONTINUITY = 5; public static final int MSG_POSITION_DISCONTINUITY = 5;
public static final int MSG_SOURCE_INFO_REFRESHED = 6; public static final int MSG_SOURCE_INFO_REFRESHED = 6;
public static final int MSG_ERROR = 7; public static final int MSG_PLAYBACK_PARAMETERS_CHANGED = 7;
public static final int MSG_ERROR = 8;
// Internal messages // Internal messages
private static final int MSG_PREPARE = 0; private static final int MSG_PREPARE = 0;
private static final int MSG_SET_PLAY_WHEN_READY = 1; private static final int MSG_SET_PLAY_WHEN_READY = 1;
private static final int MSG_DO_SOME_WORK = 2; private static final int MSG_DO_SOME_WORK = 2;
private static final int MSG_SEEK_TO = 3; private static final int MSG_SEEK_TO = 3;
private static final int MSG_STOP = 4; private static final int MSG_SET_PLAYBACK_PARAMETERS = 4;
private static final int MSG_RELEASE = 5; private static final int MSG_STOP = 5;
private static final int MSG_REFRESH_SOURCE_INFO = 6; private static final int MSG_RELEASE = 6;
private static final int MSG_PERIOD_PREPARED = 7; private static final int MSG_REFRESH_SOURCE_INFO = 7;
private static final int MSG_SOURCE_CONTINUE_LOADING_REQUESTED = 8; private static final int MSG_PERIOD_PREPARED = 8;
private static final int MSG_TRACK_SELECTION_INVALIDATED = 9; private static final int MSG_SOURCE_CONTINUE_LOADING_REQUESTED = 9;
private static final int MSG_CUSTOM = 10; private static final int MSG_TRACK_SELECTION_INVALIDATED = 10;
private static final int MSG_CUSTOM = 11;
private static final int PREPARING_SOURCE_INTERVAL_MS = 10; private static final int PREPARING_SOURCE_INTERVAL_MS = 10;
private static final int RENDERING_INTERVAL_MS = 10; private static final int RENDERING_INTERVAL_MS = 10;
...@@ -143,6 +145,7 @@ import java.io.IOException; ...@@ -143,6 +145,7 @@ import java.io.IOException;
private final Timeline.Period period; private final Timeline.Period period;
private PlaybackInfo playbackInfo; private PlaybackInfo playbackInfo;
private PlaybackParameters playbackParameters;
private Renderer rendererMediaClockSource; private Renderer rendererMediaClockSource;
private MediaClock rendererMediaClock; private MediaClock rendererMediaClock;
private MediaSource mediaSource; private MediaSource mediaSource;
...@@ -188,6 +191,7 @@ import java.io.IOException; ...@@ -188,6 +191,7 @@ import java.io.IOException;
window = new Timeline.Window(); window = new Timeline.Window();
period = new Timeline.Period(); period = new Timeline.Period();
trackSelector.init(this); trackSelector.init(this);
playbackParameters = PlaybackParameters.DEFAULT;
// Note: The documentation for Process.THREAD_PRIORITY_AUDIO that states "Applications can // Note: The documentation for Process.THREAD_PRIORITY_AUDIO that states "Applications can
// not normally change to this priority" is incorrect. // not normally change to this priority" is incorrect.
...@@ -211,6 +215,10 @@ import java.io.IOException; ...@@ -211,6 +215,10 @@ import java.io.IOException;
.sendToTarget(); .sendToTarget();
} }
public void setPlaybackParameters(PlaybackParameters playbackParameters) {
handler.obtainMessage(MSG_SET_PLAYBACK_PARAMETERS, playbackParameters).sendToTarget();
}
public void stop() { public void stop() {
handler.sendEmptyMessage(MSG_STOP); handler.sendEmptyMessage(MSG_STOP);
} }
...@@ -304,6 +312,10 @@ import java.io.IOException; ...@@ -304,6 +312,10 @@ import java.io.IOException;
seekToInternal((SeekPosition) msg.obj); seekToInternal((SeekPosition) msg.obj);
return true; return true;
} }
case MSG_SET_PLAYBACK_PARAMETERS: {
setPlaybackParametersInternal((PlaybackParameters) msg.obj);
return true;
}
case MSG_STOP: { case MSG_STOP: {
stopInternal(); stopInternal();
return true; return true;
...@@ -478,6 +490,19 @@ import java.io.IOException; ...@@ -478,6 +490,19 @@ import java.io.IOException;
maybeThrowPeriodPrepareError(); maybeThrowPeriodPrepareError();
} }
// The standalone media clock never changes playback parameters, so just check the renderer.
if (rendererMediaClock != null) {
PlaybackParameters playbackParameters = rendererMediaClock.getPlaybackParameters();
if (!playbackParameters.equals(this.playbackParameters)) {
// TODO: Make LoadControl, period transition position projection, adaptive track selection
// and potentially any time-related code in renderers take into account the playback speed.
this.playbackParameters = playbackParameters;
standaloneMediaClock.synchronize(rendererMediaClock);
eventHandler.obtainMessage(MSG_PLAYBACK_PARAMETERS_CHANGED, playbackParameters)
.sendToTarget();
}
}
long playingPeriodDurationUs = timeline.getPeriod(playingPeriodHolder.index, period) long playingPeriodDurationUs = timeline.getPeriod(playingPeriodHolder.index, period)
.getDurationUs(); .getDurationUs();
if (allRenderersEnded if (allRenderersEnded
...@@ -646,6 +671,14 @@ import java.io.IOException; ...@@ -646,6 +671,14 @@ import java.io.IOException;
} }
} }
private void setPlaybackParametersInternal(PlaybackParameters playbackParameters) {
playbackParameters = rendererMediaClock != null
? rendererMediaClock.setPlaybackParameters(playbackParameters)
: standaloneMediaClock.setPlaybackParameters(playbackParameters);
this.playbackParameters = playbackParameters;
eventHandler.obtainMessage(MSG_PLAYBACK_PARAMETERS_CHANGED, playbackParameters).sendToTarget();
}
private void stopInternal() { private void stopInternal() {
resetInternal(true); resetInternal(true);
loadControl.onStopped(); loadControl.onStopped();
...@@ -774,7 +807,7 @@ import java.io.IOException; ...@@ -774,7 +807,7 @@ import java.io.IOException;
if (sampleStream == null) { if (sampleStream == null) {
// The renderer won't be re-enabled. Sync standaloneMediaClock so that it can take // The renderer won't be re-enabled. Sync standaloneMediaClock so that it can take
// over timing responsibilities. // over timing responsibilities.
standaloneMediaClock.setPositionUs(rendererMediaClock.getPositionUs()); standaloneMediaClock.synchronize(rendererMediaClock);
} }
rendererMediaClock = null; rendererMediaClock = null;
rendererMediaClockSource = null; rendererMediaClockSource = null;
...@@ -1334,7 +1367,7 @@ import java.io.IOException; ...@@ -1334,7 +1367,7 @@ import java.io.IOException;
// is final and it's not reading ahead. // is final and it's not reading ahead.
if (renderer == rendererMediaClockSource) { if (renderer == rendererMediaClockSource) {
// Sync standaloneMediaClock so that it can take over timing responsibilities. // Sync standaloneMediaClock so that it can take over timing responsibilities.
standaloneMediaClock.setPositionUs(rendererMediaClock.getPositionUs()); standaloneMediaClock.synchronize(rendererMediaClock);
rendererMediaClock = null; rendererMediaClock = null;
rendererMediaClockSource = null; rendererMediaClockSource = null;
} }
...@@ -1380,6 +1413,7 @@ import java.io.IOException; ...@@ -1380,6 +1413,7 @@ import java.io.IOException;
} }
rendererMediaClock = mediaClock; rendererMediaClock = mediaClock;
rendererMediaClockSource = renderer; rendererMediaClockSource = renderer;
rendererMediaClock.setPlaybackParameters(playbackParameters);
} }
// Start the renderer if playing. // Start the renderer if playing.
if (playing) { if (playing) {
......
/*
* Copyright (C) 2017 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.exoplayer2;
/**
* The parameters that apply to playback.
*/
public final class PlaybackParameters {
/**
* The default playback parameters: real-time playback with no pitch modification.
*/
public static final PlaybackParameters DEFAULT = new PlaybackParameters(1f, 1f);
/**
* The factor by which playback will be sped up.
*/
public final float speed;
/**
* The factor by which the audio pitch will be scaled.
*/
public final float pitch;
private final int scaledUsPerMs;
/**
* Creates new playback parameters.
*
* @param speed The factor by which playback will be sped up.
* @param pitch The factor by which the audio pitch will be scaled.
*/
public PlaybackParameters(float speed, float pitch) {
this.speed = speed;
this.pitch = pitch;
scaledUsPerMs = Math.round(speed * 1000f);
}
/**
* Scales the millisecond duration {@code timeMs} by the playback speed, returning the result in
* microseconds.
*
* @param timeMs The time to scale, in milliseconds.
* @return The scaled time, in microseconds.
*/
public long getSpeedAdjustedDurationUs(long timeMs) {
return timeMs * scaledUsPerMs;
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null || getClass() != obj.getClass()) {
return false;
}
PlaybackParameters other = (PlaybackParameters) obj;
return this.speed == other.speed && this.pitch == other.pitch;
}
@Override
public int hashCode() {
int result = 17;
result = 31 * result + Float.floatToRawIntBits(speed);
result = 31 * result + Float.floatToRawIntBits(pitch);
return result;
}
}
...@@ -22,6 +22,7 @@ import android.media.MediaCodec; ...@@ -22,6 +22,7 @@ import android.media.MediaCodec;
import android.media.PlaybackParams; import android.media.PlaybackParams;
import android.os.Handler; import android.os.Handler;
import android.support.annotation.IntDef; import android.support.annotation.IntDef;
import android.support.annotation.Nullable;
import android.util.Log; import android.util.Log;
import android.view.Surface; import android.view.Surface;
import android.view.SurfaceHolder; import android.view.SurfaceHolder;
...@@ -145,7 +146,6 @@ public class SimpleExoPlayer implements ExoPlayer { ...@@ -145,7 +146,6 @@ public class SimpleExoPlayer implements ExoPlayer {
@C.StreamType @C.StreamType
private int audioStreamType; private int audioStreamType;
private float audioVolume; private float audioVolume;
private PlaybackParamsHolder playbackParamsHolder;
protected SimpleExoPlayer(Context context, TrackSelector trackSelector, LoadControl loadControl, protected SimpleExoPlayer(Context context, TrackSelector trackSelector, LoadControl loadControl,
DrmSessionManager<FrameworkMediaCrypto> drmSessionManager, DrmSessionManager<FrameworkMediaCrypto> drmSessionManager,
...@@ -344,37 +344,20 @@ public class SimpleExoPlayer implements ExoPlayer { ...@@ -344,37 +344,20 @@ public class SimpleExoPlayer implements ExoPlayer {
/** /**
* Sets the {@link PlaybackParams} governing audio playback. * Sets the {@link PlaybackParams} governing audio playback.
* *
* @deprecated Use {@link #setPlaybackParameters(PlaybackParameters)}.
* @param params The {@link PlaybackParams}, or null to clear any previously set parameters. * @param params The {@link PlaybackParams}, or null to clear any previously set parameters.
*/ */
@Deprecated
@TargetApi(23) @TargetApi(23)
public void setPlaybackParams(PlaybackParams params) { public void setPlaybackParams(@Nullable PlaybackParams params) {
PlaybackParameters playbackParameters;
if (params != null) { if (params != null) {
// The audio renderers will call this on the playback thread to ensure they can query
// parameters without failure. We do the same up front, which is redundant except that it
// ensures an immediate call to getPlaybackParams will retrieve the instance with defaults
// allowed, rather than this change becoming visible sometime later once the audio renderers
// receive the parameters.
params.allowDefaults(); params.allowDefaults();
playbackParamsHolder = new PlaybackParamsHolder(params); playbackParameters = new PlaybackParameters(params.getSpeed(), params.getPitch());
} else { } else {
playbackParamsHolder = null; playbackParameters = null;
} }
ExoPlayerMessage[] messages = new ExoPlayerMessage[audioRendererCount]; setPlaybackParameters(playbackParameters);
int count = 0;
for (Renderer renderer : renderers) {
if (renderer.getTrackType() == C.TRACK_TYPE_AUDIO) {
messages[count++] = new ExoPlayerMessage(renderer, C.MSG_SET_PLAYBACK_PARAMS, params);
}
}
player.sendMessages(messages);
}
/**
* Returns the {@link PlaybackParams} governing audio playback, or null if not set.
*/
@TargetApi(23)
public PlaybackParams getPlaybackParams() {
return playbackParamsHolder == null ? null : playbackParamsHolder.params;
} }
/** /**
...@@ -520,6 +503,16 @@ public class SimpleExoPlayer implements ExoPlayer { ...@@ -520,6 +503,16 @@ public class SimpleExoPlayer implements ExoPlayer {
} }
@Override @Override
public void setPlaybackParameters(PlaybackParameters playbackParameters) {
player.setPlaybackParameters(playbackParameters);
}
@Override
public PlaybackParameters getPlaybackParameters() {
return player.getPlaybackParameters();
}
@Override
public void stop() { public void stop() {
player.stop(); player.stop();
} }
...@@ -1024,15 +1017,4 @@ public class SimpleExoPlayer implements ExoPlayer { ...@@ -1024,15 +1017,4 @@ public class SimpleExoPlayer implements ExoPlayer {
} }
@TargetApi(23)
private static final class PlaybackParamsHolder {
public final PlaybackParams params;
public PlaybackParamsHolder(PlaybackParams params) {
this.params = params;
}
}
} }
...@@ -19,12 +19,12 @@ import android.annotation.TargetApi; ...@@ -19,12 +19,12 @@ import android.annotation.TargetApi;
import android.media.MediaCodec; import android.media.MediaCodec;
import android.media.MediaCrypto; import android.media.MediaCrypto;
import android.media.MediaFormat; import android.media.MediaFormat;
import android.media.PlaybackParams;
import android.media.audiofx.Virtualizer; import android.media.audiofx.Virtualizer;
import android.os.Handler; import android.os.Handler;
import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.ExoPlaybackException; import com.google.android.exoplayer2.ExoPlaybackException;
import com.google.android.exoplayer2.Format; import com.google.android.exoplayer2.Format;
import com.google.android.exoplayer2.PlaybackParameters;
import com.google.android.exoplayer2.audio.AudioRendererEventListener.EventDispatcher; import com.google.android.exoplayer2.audio.AudioRendererEventListener.EventDispatcher;
import com.google.android.exoplayer2.drm.DrmSessionManager; import com.google.android.exoplayer2.drm.DrmSessionManager;
import com.google.android.exoplayer2.drm.FrameworkMediaCrypto; import com.google.android.exoplayer2.drm.FrameworkMediaCrypto;
...@@ -346,6 +346,16 @@ public class MediaCodecAudioRenderer extends MediaCodecRenderer implements Media ...@@ -346,6 +346,16 @@ public class MediaCodecAudioRenderer extends MediaCodecRenderer implements Media
} }
@Override @Override
public PlaybackParameters setPlaybackParameters(PlaybackParameters playbackParameters) {
return audioTrack.setPlaybackParameters(playbackParameters);
}
@Override
public PlaybackParameters getPlaybackParameters() {
return audioTrack.getPlaybackParameters();
}
@Override
protected boolean processOutputBuffer(long positionUs, long elapsedRealtimeUs, MediaCodec codec, protected boolean processOutputBuffer(long positionUs, long elapsedRealtimeUs, MediaCodec codec,
ByteBuffer buffer, int bufferIndex, int bufferFlags, long bufferPresentationTimeUs, ByteBuffer buffer, int bufferIndex, int bufferFlags, long bufferPresentationTimeUs,
boolean shouldSkip) throws ExoPlaybackException { boolean shouldSkip) throws ExoPlaybackException {
...@@ -389,9 +399,6 @@ public class MediaCodecAudioRenderer extends MediaCodecRenderer implements Media ...@@ -389,9 +399,6 @@ public class MediaCodecAudioRenderer extends MediaCodecRenderer implements Media
case C.MSG_SET_VOLUME: case C.MSG_SET_VOLUME:
audioTrack.setVolume((Float) message); audioTrack.setVolume((Float) message);
break; break;
case C.MSG_SET_PLAYBACK_PARAMS:
audioTrack.setPlaybackParams((PlaybackParams) message);
break;
case C.MSG_SET_STREAM_TYPE: case C.MSG_SET_STREAM_TYPE:
@C.StreamType int streamType = (Integer) message; @C.StreamType int streamType = (Integer) message;
audioTrack.setStreamType(streamType); audioTrack.setStreamType(streamType);
......
...@@ -15,7 +15,6 @@ ...@@ -15,7 +15,6 @@
*/ */
package com.google.android.exoplayer2.audio; package com.google.android.exoplayer2.audio;
import android.media.PlaybackParams;
import android.media.audiofx.Virtualizer; import android.media.audiofx.Virtualizer;
import android.os.Handler; import android.os.Handler;
import android.os.Looper; import android.os.Looper;
...@@ -26,6 +25,7 @@ import com.google.android.exoplayer2.C; ...@@ -26,6 +25,7 @@ import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.ExoPlaybackException; import com.google.android.exoplayer2.ExoPlaybackException;
import com.google.android.exoplayer2.Format; import com.google.android.exoplayer2.Format;
import com.google.android.exoplayer2.FormatHolder; import com.google.android.exoplayer2.FormatHolder;
import com.google.android.exoplayer2.PlaybackParameters;
import com.google.android.exoplayer2.audio.AudioRendererEventListener.EventDispatcher; import com.google.android.exoplayer2.audio.AudioRendererEventListener.EventDispatcher;
import com.google.android.exoplayer2.decoder.DecoderCounters; import com.google.android.exoplayer2.decoder.DecoderCounters;
import com.google.android.exoplayer2.decoder.DecoderInputBuffer; import com.google.android.exoplayer2.decoder.DecoderInputBuffer;
...@@ -435,6 +435,16 @@ public abstract class SimpleDecoderAudioRenderer extends BaseRenderer implements ...@@ -435,6 +435,16 @@ public abstract class SimpleDecoderAudioRenderer extends BaseRenderer implements
} }
@Override @Override
public PlaybackParameters setPlaybackParameters(PlaybackParameters playbackParameters) {
return audioTrack.setPlaybackParameters(playbackParameters);
}
@Override
public PlaybackParameters getPlaybackParameters() {
return audioTrack.getPlaybackParameters();
}
@Override
protected void onEnabled(boolean joining) throws ExoPlaybackException { protected void onEnabled(boolean joining) throws ExoPlaybackException {
decoderCounters = new DecoderCounters(); decoderCounters = new DecoderCounters();
eventDispatcher.enabled(decoderCounters); eventDispatcher.enabled(decoderCounters);
...@@ -585,9 +595,6 @@ public abstract class SimpleDecoderAudioRenderer extends BaseRenderer implements ...@@ -585,9 +595,6 @@ public abstract class SimpleDecoderAudioRenderer extends BaseRenderer implements
case C.MSG_SET_VOLUME: case C.MSG_SET_VOLUME:
audioTrack.setVolume((Float) message); audioTrack.setVolume((Float) message);
break; break;
case C.MSG_SET_PLAYBACK_PARAMS:
audioTrack.setPlaybackParams((PlaybackParams) message);
break;
case C.MSG_SET_STREAM_TYPE: case C.MSG_SET_STREAM_TYPE:
@C.StreamType int streamType = (Integer) message; @C.StreamType int streamType = (Integer) message;
audioTrack.setStreamType(streamType); audioTrack.setStreamType(streamType);
......
/*
* Copyright (C) 2017 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.exoplayer2.audio;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.C.Encoding;
import com.google.android.exoplayer2.Format;
import com.google.android.exoplayer2.util.Util;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
/**
* An {@link AudioProcessor} that uses the Sonic library to modify the speed/pitch of audio.
*/
// TODO: Make public once it is possible to override AudioTrack's position calculations.
/* package */ final class SonicAudioProcessor implements AudioProcessor {
/**
* The maximum allowed playback speed in {@link #setSpeed(float)}.
*/
public static final float MAXIMUM_SPEED = 8.0f;
/**
* The minimum allowed playback speed in {@link #setSpeed(float)}.
*/
public static final float MINIMUM_SPEED = 0.1f;
/**
* The maximum allowed pitch in {@link #setPitch(float)}.
*/
public static final float MAXIMUM_PITCH = 8.0f;
/**
* The minimum allowed pitch in {@link #setPitch(float)}.
*/
public static final float MINIMUM_PITCH = 0.1f;
/**
* The threshold below which the difference between two pitch/speed factors is negligible.
*/
private static final float CLOSE_THRESHOLD = 0.01f;
private static final byte[] EMPTY_ARRAY = new byte[0];
private int channelCount;
private int sampleRateHz;
private Sonic sonic;
private float speed;
private float pitch;
private byte[] inputArray;
private ByteBuffer buffer;
private byte[] bufferArray;
private ByteBuffer outputBuffer;
private long inputBytes;
private long outputBytes;
private boolean inputEnded;
/**
* Creates a new Sonic audio processor.
*/
public SonicAudioProcessor() {
speed = 1f;
pitch = 1f;
channelCount = Format.NO_VALUE;
sampleRateHz = Format.NO_VALUE;
buffer = EMPTY_BUFFER;
outputBuffer = EMPTY_BUFFER;
inputArray = EMPTY_ARRAY;
bufferArray = EMPTY_ARRAY;
}
/**
* Sets the playback speed. The new speed will take effect after a call to {@link #flush()}.
*
* @param speed The requested new playback speed.
* @return The actual new playback speed.
*/
public float setSpeed(float speed) {
this.speed = Util.constrainValue(speed, MINIMUM_SPEED, MAXIMUM_SPEED);
return this.speed;
}
/**
* Sets the playback pitch. The new pitch will take effect after a call to {@link #flush()}.
*
* @param pitch The requested new pitch.
* @return The actual new pitch.
*/
public float setPitch(float pitch) {
this.pitch = Util.constrainValue(pitch, MINIMUM_PITCH, MAXIMUM_PITCH);
return pitch;
}
/**
* Returns the number of input frames corresponding to the specified number of output frames.
*/
public long getInputFrames(long outputFrames) {
// Sonic produces output data as soon as input is queued.
return outputBytes == 0 ? 0 : Util.scaleLargeTimestamp(outputFrames, inputBytes, outputBytes);
}
@Override
public boolean configure(int sampleRateHz, int channelCount, @Encoding int encoding)
throws UnhandledFormatException {
if (encoding != C.ENCODING_PCM_16BIT) {
throw new UnhandledFormatException(sampleRateHz, channelCount, encoding);
}
if (this.sampleRateHz == sampleRateHz && this.channelCount == channelCount) {
return false;
}
this.sampleRateHz = sampleRateHz;
this.channelCount = channelCount;
return true;
}
@Override
public boolean isActive() {
return Math.abs(speed - 1f) >= CLOSE_THRESHOLD || Math.abs(pitch - 1f) >= CLOSE_THRESHOLD;
}
@Override
public int getOutputChannelCount() {
return channelCount;
}
@Override
public int getOutputEncoding() {
return C.ENCODING_PCM_16BIT;
}
@Override
public void queueInput(ByteBuffer inputBuffer) {
// TODO: Remove this extra copy.
int inputBytesToRead = inputBuffer.remaining();
if (inputArray == null || inputArray.length < inputBytesToRead) {
inputArray = new byte[inputBytesToRead];
}
inputBuffer.get(inputArray, 0, inputBytesToRead);
sonic.writeBytesToStream(inputArray, inputBytesToRead);
int outputSize = sonic.samplesAvailable() * channelCount * 2;
if (buffer.capacity() < outputSize) {
buffer = ByteBuffer.allocateDirect(outputSize).order(ByteOrder.nativeOrder());
bufferArray = new byte[outputSize];
} else {
buffer.clear();
}
inputBytes += inputBytesToRead;
int outputBytesRead = sonic.readBytesFromStream(bufferArray, outputSize);
buffer.put(bufferArray, 0, outputBytesRead);
buffer.flip();
outputBytes += outputSize;
outputBuffer = buffer;
}
@Override
public void queueEndOfStream() {
sonic.flushStream();
inputEnded = true;
}
@Override
public ByteBuffer getOutput() {
ByteBuffer outputBuffer = this.outputBuffer;
this.outputBuffer = EMPTY_BUFFER;
return outputBuffer;
}
@Override
public boolean isEnded() {
return inputEnded && (sonic == null || sonic.samplesAvailable() == 0);
}
@Override
public void flush() {
sonic = new Sonic(sampleRateHz, channelCount);
sonic.setSpeed(speed);
sonic.setPitch(pitch);
outputBuffer = EMPTY_BUFFER;
inputBytes = 0;
outputBytes = 0;
inputEnded = false;
}
@Override
public void release() {
sonic = null;
buffer = EMPTY_BUFFER;
outputBuffer = EMPTY_BUFFER;
inputArray = EMPTY_ARRAY;
bufferArray = EMPTY_ARRAY;
}
}
...@@ -15,6 +15,8 @@ ...@@ -15,6 +15,8 @@
*/ */
package com.google.android.exoplayer2.util; package com.google.android.exoplayer2.util;
import com.google.android.exoplayer2.PlaybackParameters;
/** /**
* Tracks the progression of media time. * Tracks the progression of media time.
*/ */
...@@ -25,4 +27,18 @@ public interface MediaClock { ...@@ -25,4 +27,18 @@ public interface MediaClock {
*/ */
long getPositionUs(); long getPositionUs();
/**
* Attempts to set the playback parameters and returns the active playback parameters, which may
* differ from those passed in.
*
* @param playbackParameters The playback parameters.
* @return The active playback parameters.
*/
PlaybackParameters setPlaybackParameters(PlaybackParameters playbackParameters);
/**
* Returns the active playback parameters.
*/
PlaybackParameters getPlaybackParameters();
} }
...@@ -16,33 +16,34 @@ ...@@ -16,33 +16,34 @@
package com.google.android.exoplayer2.util; package com.google.android.exoplayer2.util;
import android.os.SystemClock; import android.os.SystemClock;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.PlaybackParameters;
/** /**
* A standalone {@link MediaClock}. The clock can be started, stopped and its time can be set and * A {@link MediaClock} whose position advances with real time based on the playback parameters when
* retrieved. When started, this clock is based on {@link SystemClock#elapsedRealtime()}. * started.
*/ */
public final class StandaloneMediaClock implements MediaClock { public final class StandaloneMediaClock implements MediaClock {
private boolean started; private boolean started;
private long baseUs;
private long baseElapsedMs;
private PlaybackParameters playbackParameters;
/** /**
* The media time when the clock was last set or stopped. * Creates a new standalone media clock.
*/ */
private long positionUs; public StandaloneMediaClock() {
playbackParameters = PlaybackParameters.DEFAULT;
/** }
* The difference between {@link SystemClock#elapsedRealtime()} and {@link #positionUs}
* when the clock was last set or started.
*/
private long deltaUs;
/** /**
* Starts the clock. Does nothing if the clock is already started. * Starts the clock. Does nothing if the clock is already started.
*/ */
public void start() { public void start() {
if (!started) { if (!started) {
baseElapsedMs = SystemClock.elapsedRealtime();
started = true; started = true;
deltaUs = elapsedRealtimeMinus(positionUs);
} }
} }
...@@ -51,26 +52,60 @@ public final class StandaloneMediaClock implements MediaClock { ...@@ -51,26 +52,60 @@ public final class StandaloneMediaClock implements MediaClock {
*/ */
public void stop() { public void stop() {
if (started) { if (started) {
positionUs = elapsedRealtimeMinus(deltaUs); setPositionUs(getPositionUs());
started = false; started = false;
} }
} }
/** /**
* @param timeUs The position to set in microseconds. * Sets the clock's position.
*
* @param positionUs The position to set in microseconds.
*/ */
public void setPositionUs(long timeUs) { public void setPositionUs(long positionUs) {
this.positionUs = timeUs; baseUs = positionUs;
deltaUs = elapsedRealtimeMinus(timeUs); if (started) {
baseElapsedMs = SystemClock.elapsedRealtime();
}
}
/**
* Synchronizes this clock with the current state of {@code clock}.
*
* @param clock The clock with which to synchronize.
*/
public void synchronize(MediaClock clock) {
setPositionUs(clock.getPositionUs());
playbackParameters = clock.getPlaybackParameters();
} }
@Override @Override
public long getPositionUs() { public long getPositionUs() {
return started ? elapsedRealtimeMinus(deltaUs) : positionUs; long positionUs = baseUs;
if (started) {
long elapsedSinceBaseMs = SystemClock.elapsedRealtime() - baseElapsedMs;
if (playbackParameters.speed == 1f) {
positionUs += C.msToUs(elapsedSinceBaseMs);
} else {
positionUs += playbackParameters.getSpeedAdjustedDurationUs(elapsedSinceBaseMs);
}
}
return positionUs;
}
@Override
public PlaybackParameters setPlaybackParameters(PlaybackParameters playbackParameters) {
// Store the current position as the new base, in case the playback speed has changed.
if (started) {
setPositionUs(getPositionUs());
}
this.playbackParameters = playbackParameters;
return playbackParameters;
} }
private long elapsedRealtimeMinus(long toSubtractUs) { @Override
return SystemClock.elapsedRealtime() * 1000 - toSubtractUs; public PlaybackParameters getPlaybackParameters() {
return playbackParameters;
} }
} }
...@@ -310,6 +310,18 @@ public final class Util { ...@@ -310,6 +310,18 @@ public final class Util {
} }
/** /**
* Constrains a value to the specified bounds.
*
* @param value The value to constrain.
* @param min The lower bound.
* @param max The upper bound.
* @return The constrained value {@code Math.max(min, Math.min(value, max))}.
*/
public static float constrainValue(float value, float min, float max) {
return Math.max(min, Math.min(value, max));
}
/**
* Returns the index of the largest element in {@code array} that is less than (or optionally * Returns the index of the largest element in {@code array} that is less than (or optionally
* equal to) a specified {@code value}. * equal to) a specified {@code value}.
* <p> * <p>
......
...@@ -19,6 +19,7 @@ import android.widget.TextView; ...@@ -19,6 +19,7 @@ import android.widget.TextView;
import com.google.android.exoplayer2.ExoPlaybackException; import com.google.android.exoplayer2.ExoPlaybackException;
import com.google.android.exoplayer2.ExoPlayer; import com.google.android.exoplayer2.ExoPlayer;
import com.google.android.exoplayer2.Format; import com.google.android.exoplayer2.Format;
import com.google.android.exoplayer2.PlaybackParameters;
import com.google.android.exoplayer2.SimpleExoPlayer; import com.google.android.exoplayer2.SimpleExoPlayer;
import com.google.android.exoplayer2.Timeline; import com.google.android.exoplayer2.Timeline;
import com.google.android.exoplayer2.decoder.DecoderCounters; import com.google.android.exoplayer2.decoder.DecoderCounters;
...@@ -91,6 +92,11 @@ public final class DebugTextViewHelper implements Runnable, ExoPlayer.EventListe ...@@ -91,6 +92,11 @@ public final class DebugTextViewHelper implements Runnable, ExoPlayer.EventListe
} }
@Override @Override
public void onPlaybackParametersChanged(PlaybackParameters playbackParameters) {
// Do nothing.
}
@Override
public void onTimelineChanged(Timeline timeline, Object manifest) { public void onTimelineChanged(Timeline timeline, Object manifest) {
// Do nothing. // Do nothing.
} }
......
...@@ -29,6 +29,7 @@ import android.widget.TextView; ...@@ -29,6 +29,7 @@ import android.widget.TextView;
import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.ExoPlaybackException; import com.google.android.exoplayer2.ExoPlaybackException;
import com.google.android.exoplayer2.ExoPlayer; import com.google.android.exoplayer2.ExoPlayer;
import com.google.android.exoplayer2.PlaybackParameters;
import com.google.android.exoplayer2.Timeline; import com.google.android.exoplayer2.Timeline;
import com.google.android.exoplayer2.source.TrackGroupArray; import com.google.android.exoplayer2.source.TrackGroupArray;
import com.google.android.exoplayer2.trackselection.TrackSelectionArray; import com.google.android.exoplayer2.trackselection.TrackSelectionArray;
...@@ -767,6 +768,11 @@ public class PlaybackControlView extends FrameLayout { ...@@ -767,6 +768,11 @@ public class PlaybackControlView extends FrameLayout {
} }
@Override @Override
public void onPlaybackParametersChanged(PlaybackParameters playbackParameters) {
// Do nothing.
}
@Override
public void onTimelineChanged(Timeline timeline, Object manifest) { public void onTimelineChanged(Timeline timeline, Object manifest) {
updateNavigation(); updateNavigation();
updateProgress(); updateProgress();
......
...@@ -34,6 +34,7 @@ import android.widget.ImageView; ...@@ -34,6 +34,7 @@ import android.widget.ImageView;
import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.ExoPlaybackException; import com.google.android.exoplayer2.ExoPlaybackException;
import com.google.android.exoplayer2.ExoPlayer; import com.google.android.exoplayer2.ExoPlayer;
import com.google.android.exoplayer2.PlaybackParameters;
import com.google.android.exoplayer2.SimpleExoPlayer; import com.google.android.exoplayer2.SimpleExoPlayer;
import com.google.android.exoplayer2.Timeline; import com.google.android.exoplayer2.Timeline;
import com.google.android.exoplayer2.metadata.Metadata; import com.google.android.exoplayer2.metadata.Metadata;
...@@ -742,6 +743,11 @@ public final class SimpleExoPlayerView extends FrameLayout { ...@@ -742,6 +743,11 @@ public final class SimpleExoPlayerView extends FrameLayout {
} }
@Override @Override
public void onPlaybackParametersChanged(PlaybackParameters playbackParameters) {
// Do nothing.
}
@Override
public void onTimelineChanged(Timeline timeline, Object manifest) { public void onTimelineChanged(Timeline timeline, Object manifest) {
// Do nothing. // Do nothing.
} }
......
...@@ -24,6 +24,7 @@ import com.google.android.exoplayer2.ExoPlaybackException; ...@@ -24,6 +24,7 @@ import com.google.android.exoplayer2.ExoPlaybackException;
import com.google.android.exoplayer2.ExoPlayer; import com.google.android.exoplayer2.ExoPlayer;
import com.google.android.exoplayer2.ExoPlayerFactory; import com.google.android.exoplayer2.ExoPlayerFactory;
import com.google.android.exoplayer2.Format; import com.google.android.exoplayer2.Format;
import com.google.android.exoplayer2.PlaybackParameters;
import com.google.android.exoplayer2.SimpleExoPlayer; import com.google.android.exoplayer2.SimpleExoPlayer;
import com.google.android.exoplayer2.Timeline; import com.google.android.exoplayer2.Timeline;
import com.google.android.exoplayer2.audio.AudioRendererEventListener; import com.google.android.exoplayer2.audio.AudioRendererEventListener;
...@@ -224,6 +225,11 @@ public abstract class ExoHostedTest implements HostedTest, ExoPlayer.EventListen ...@@ -224,6 +225,11 @@ public abstract class ExoHostedTest implements HostedTest, ExoPlayer.EventListen
} }
@Override @Override
public final void onPlaybackParametersChanged(PlaybackParameters playbackParameters) {
// Do nothing.
}
@Override
public final void onTimelineChanged(Timeline timeline, Object manifest) { public final void onTimelineChanged(Timeline timeline, Object manifest) {
// Do nothing. // Do nothing.
} }
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment