/*
* Copyright 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 android.media;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.TestApi;
import android.os.Parcel;
import android.os.Parcelable;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.AutoCloseable;
import java.lang.ref.WeakReference;
import java.util.Arrays;
import java.util.Objects;
The VolumeShaper
class is used to automatically control audio volume during media playback, allowing simple implementation of transition effects and ducking. It is created from implementations of VolumeAutomation
, such as MediaPlayer
and AudioTrack
(referred to as "players" below), by MediaPlayer.createVolumeShaper
or AudioTrack.createVolumeShaper
. A VolumeShaper
is intended for short volume changes. If the audio output sink changes during a VolumeShaper
transition, the precise curve position may be lost, and the VolumeShaper
may advance to the end of the curve for the new audio output sink. The VolumeShaper
appears as an additional scaling on the audio output, and adjusts independently of track or stream volume controls. /**
* The {@code VolumeShaper} class is used to automatically control audio volume during media
* playback, allowing simple implementation of transition effects and ducking.
* It is created from implementations of {@code VolumeAutomation},
* such as {@code MediaPlayer} and {@code AudioTrack} (referred to as "players" below),
* by {@link MediaPlayer#createVolumeShaper} or {@link AudioTrack#createVolumeShaper}.
*
* A {@code VolumeShaper} is intended for short volume changes.
* If the audio output sink changes during
* a {@code VolumeShaper} transition, the precise curve position may be lost, and the
* {@code VolumeShaper} may advance to the end of the curve for the new audio output sink.
*
* The {@code VolumeShaper} appears as an additional scaling on the audio output,
* and adjusts independently of track or stream volume controls.
*/
public final class VolumeShaper implements AutoCloseable {
/* member variables */
private int mId;
private final WeakReference<PlayerBase> mWeakPlayerBase;
/* package */ VolumeShaper(
@NonNull Configuration configuration, @NonNull PlayerBase playerBase) {
mWeakPlayerBase = new WeakReference<PlayerBase>(playerBase);
mId = applyPlayer(configuration, new Operation.Builder().defer().build());
}
/* package */ int getId() {
return mId;
}
Applies the Operation
to the VolumeShaper
. Applying Operation.PLAY
after PLAY
or Operation.REVERSE
after REVERSE
has no effect. Applying Operation.PLAY
when the player hasn't started will synchronously start the VolumeShaper
when playback begins. Params: - operation – the
operation
to apply.
Throws: - IllegalStateException – if the player is uninitialized or if there is a critical failure. In that case, the
VolumeShaper
should be recreated.
/**
* Applies the {@link VolumeShaper.Operation} to the {@code VolumeShaper}.
*
* Applying {@link VolumeShaper.Operation#PLAY} after {@code PLAY}
* or {@link VolumeShaper.Operation#REVERSE} after
* {@code REVERSE} has no effect.
*
* Applying {@link VolumeShaper.Operation#PLAY} when the player
* hasn't started will synchronously start the {@code VolumeShaper} when
* playback begins.
*
* @param operation the {@code operation} to apply.
* @throws IllegalStateException if the player is uninitialized or if there
* is a critical failure. In that case, the {@code VolumeShaper} should be
* recreated.
*/
public void apply(@NonNull Operation operation) {
/* void */ applyPlayer(new VolumeShaper.Configuration(mId), operation);
}
Replaces the current VolumeShaper
configuration
with a new configuration
. This allows the user to change the volume shape while the existing VolumeShaper
is in effect. The effect of replace()
is similar to an atomic close of the existing VolumeShaper
and creation of a new VolumeShaper
. If the operation
is Operation.PLAY
then the new curve starts immediately. If the operation
is Operation.REVERSE
, then the new curve will be delayed until PLAY
is applied. Params: - configuration – the new
configuration
to use. - operation – the
operation
to apply to the VolumeShaper
- join – if true, match the start volume of the new
configuration
to the current volume of the existing VolumeShaper
, to avoid discontinuity.
Throws: - IllegalStateException – if the player is uninitialized or if there is a critical failure. In that case, the
VolumeShaper
should be recreated.
/**
* Replaces the current {@code VolumeShaper}
* {@code configuration} with a new {@code configuration}.
*
* This allows the user to change the volume shape
* while the existing {@code VolumeShaper} is in effect.
*
* The effect of {@code replace()} is similar to an atomic close of
* the existing {@code VolumeShaper} and creation of a new {@code VolumeShaper}.
*
* If the {@code operation} is {@link VolumeShaper.Operation#PLAY} then the
* new curve starts immediately.
*
* If the {@code operation} is
* {@link VolumeShaper.Operation#REVERSE}, then the new curve will
* be delayed until {@code PLAY} is applied.
*
* @param configuration the new {@code configuration} to use.
* @param operation the {@code operation} to apply to the {@code VolumeShaper}
* @param join if true, match the start volume of the
* new {@code configuration} to the current volume of the existing
* {@code VolumeShaper}, to avoid discontinuity.
* @throws IllegalStateException if the player is uninitialized or if there
* is a critical failure. In that case, the {@code VolumeShaper} should be
* recreated.
*/
public void replace(
@NonNull Configuration configuration, @NonNull Operation operation, boolean join) {
mId = applyPlayer(
configuration,
new Operation.Builder(operation).replace(mId, join).build());
}
Returns the current volume scale attributable to the VolumeShaper
. This is the last volume from the VolumeShaper
used for the player, or the initial volume if the VolumeShaper
hasn't been started with Operation.PLAY
. Throws: - IllegalStateException – if the player is uninitialized or if there is a critical failure. In that case, the
VolumeShaper
should be recreated.
Returns: the volume, linearly represented as a value between 0.f and 1.f.
/**
* Returns the current volume scale attributable to the {@code VolumeShaper}.
*
* This is the last volume from the {@code VolumeShaper} used for the player,
* or the initial volume if the {@code VolumeShaper} hasn't been started with
* {@link VolumeShaper.Operation#PLAY}.
*
* @return the volume, linearly represented as a value between 0.f and 1.f.
* @throws IllegalStateException if the player is uninitialized or if there
* is a critical failure. In that case, the {@code VolumeShaper} should be
* recreated.
*/
public float getVolume() {
return getStatePlayer(mId).getVolume();
}
Releases the VolumeShaper
object; any volume scale due to the VolumeShaper
is removed after closing. If the volume does not reach 1.f when the VolumeShaper
is closed (or finalized), there may be an abrupt change of volume. close()
may be safely called after a prior close()
. This class implements the Java AutoClosable
interface and may be used with try-with-resources. /**
* Releases the {@code VolumeShaper} object; any volume scale due to the
* {@code VolumeShaper} is removed after closing.
*
* If the volume does not reach 1.f when the {@code VolumeShaper} is closed
* (or finalized), there may be an abrupt change of volume.
*
* {@code close()} may be safely called after a prior {@code close()}.
* This class implements the Java {@code AutoClosable} interface and
* may be used with try-with-resources.
*/
@Override
public void close() {
try {
/* void */ applyPlayer(
new VolumeShaper.Configuration(mId),
new Operation.Builder().terminate().build());
} catch (IllegalStateException ise) {
; // ok
}
if (mWeakPlayerBase != null) {
mWeakPlayerBase.clear();
}
}
@Override
protected void finalize() {
close(); // ensure we remove the native VolumeShaper
}
Internal call to apply the configuration
and operation
to the player. Returns a valid shaper id or throws the appropriate exception. Params: - configuration –
- operation –
Throws: - IllegalStateException – if the player has been deallocated or is uninitialized.
Returns: id a non-negative shaper id.
/**
* Internal call to apply the {@code configuration} and {@code operation} to the player.
* Returns a valid shaper id or throws the appropriate exception.
* @param configuration
* @param operation
* @return id a non-negative shaper id.
* @throws IllegalStateException if the player has been deallocated or is uninitialized.
*/
private int applyPlayer(
@NonNull VolumeShaper.Configuration configuration,
@NonNull VolumeShaper.Operation operation) {
final int id;
if (mWeakPlayerBase != null) {
PlayerBase player = mWeakPlayerBase.get();
if (player == null) {
throw new IllegalStateException("player deallocated");
}
id = player.playerApplyVolumeShaper(configuration, operation);
} else {
throw new IllegalStateException("uninitialized shaper");
}
if (id < 0) {
// TODO - get INVALID_OPERATION from platform.
final int VOLUME_SHAPER_INVALID_OPERATION = -38; // must match with platform
// Due to RPC handling, we translate integer codes to exceptions right before
// delivering to the user.
if (id == VOLUME_SHAPER_INVALID_OPERATION) {
throw new IllegalStateException("player or VolumeShaper deallocated");
} else {
throw new IllegalArgumentException("invalid configuration or operation: " + id);
}
}
return id;
}
Internal call to retrieve the current VolumeShaper
state. Params: - id –
Throws: - IllegalStateException – if the player has been deallocated or is uninitialized.
Returns: the current VolumeShaper.State
/**
* Internal call to retrieve the current {@code VolumeShaper} state.
* @param id
* @return the current {@code VolumeShaper.State}
* @throws IllegalStateException if the player has been deallocated or is uninitialized.
*/
private @NonNull VolumeShaper.State getStatePlayer(int id) {
final VolumeShaper.State state;
if (mWeakPlayerBase != null) {
PlayerBase player = mWeakPlayerBase.get();
if (player == null) {
throw new IllegalStateException("player deallocated");
}
state = player.playerGetVolumeShaperState(id);
} else {
throw new IllegalStateException("uninitialized shaper");
}
if (state == null) {
throw new IllegalStateException("shaper cannot be found");
}
return state;
}
The VolumeShaper.Configuration
class contains curve and duration information. It is constructed by the Builder
. A VolumeShaper.Configuration
is used by
VolumeAutomation.createVolumeShaper(Configuration)
to create a VolumeShaper
and by
VolumeShaper.replace(Configuration, Operation, boolean)
to replace an existing configuration
.
The AudioTrack
and MediaPlayer
classes implement the VolumeAutomation
interface.
/**
* The {@code VolumeShaper.Configuration} class contains curve
* and duration information.
* It is constructed by the {@link VolumeShaper.Configuration.Builder}.
* <p>
* A {@code VolumeShaper.Configuration} is used by
* {@link VolumeAutomation#createVolumeShaper(Configuration)
* VolumeAutomation.createVolumeShaper(Configuration)} to create
* a {@code VolumeShaper} and
* by {@link VolumeShaper#replace(Configuration, Operation, boolean)
* VolumeShaper.replace(Configuration, Operation, boolean)}
* to replace an existing {@code configuration}.
* <p>
* The {@link AudioTrack} and {@link MediaPlayer} classes implement
* the {@link VolumeAutomation} interface.
*/
public static final class Configuration implements Parcelable {
private static final int MAXIMUM_CURVE_POINTS = 16;
Returns the maximum number of curve points allowed for setCurve.setCurve(float[], float[])
. /**
* Returns the maximum number of curve points allowed for
* {@link VolumeShaper.Builder#setCurve(float[], float[])}.
*/
public static int getMaximumCurvePoints() {
return MAXIMUM_CURVE_POINTS;
}
// These values must match the native VolumeShaper::Configuration::Type
@hide
/** @hide */
@IntDef({
TYPE_ID,
TYPE_SCALE,
})
@Retention(RetentionPolicy.SOURCE)
public @interface Type {}
Specifies a VolumeShaper
handle created by VolumeShaper(int)
from an id returned by setVolumeShaper()
. The type, curve, etc. may not be queried from a VolumeShaper
object of this type; the handle is used to identify and change the operation of an existing VolumeShaper
sent to the player. /**
* Specifies a {@link VolumeShaper} handle created by {@link #VolumeShaper(int)}
* from an id returned by {@code setVolumeShaper()}.
* The type, curve, etc. may not be queried from
* a {@code VolumeShaper} object of this type;
* the handle is used to identify and change the operation of
* an existing {@code VolumeShaper} sent to the player.
*/
/* package */ static final int TYPE_ID = 0;
Specifies a VolumeShaper
to be used as an additional scale to the current volume. This is created by the Builder
. /**
* Specifies a {@link VolumeShaper} to be used
* as an additional scale to the current volume.
* This is created by the {@link VolumeShaper.Builder}.
*/
/* package */ static final int TYPE_SCALE = 1;
// These values must match the native InterpolatorType enumeration.
@hide
/** @hide */
@IntDef({
INTERPOLATOR_TYPE_STEP,
INTERPOLATOR_TYPE_LINEAR,
INTERPOLATOR_TYPE_CUBIC,
INTERPOLATOR_TYPE_CUBIC_MONOTONIC,
})
@Retention(RetentionPolicy.SOURCE)
public @interface InterpolatorType {}
Stepwise volume curve.
/**
* Stepwise volume curve.
*/
public static final int INTERPOLATOR_TYPE_STEP = 0;
Linear interpolated volume curve.
/**
* Linear interpolated volume curve.
*/
public static final int INTERPOLATOR_TYPE_LINEAR = 1;
Cubic interpolated volume curve.
This is default if unspecified.
/**
* Cubic interpolated volume curve.
* This is default if unspecified.
*/
public static final int INTERPOLATOR_TYPE_CUBIC = 2;
Cubic interpolated volume curve
that preserves local monotonicity.
So long as the control points are locally monotonic,
the curve interpolation between those points are monotonic.
This is useful for cubic spline interpolated
volume ramps and ducks.
/**
* Cubic interpolated volume curve
* that preserves local monotonicity.
* So long as the control points are locally monotonic,
* the curve interpolation between those points are monotonic.
* This is useful for cubic spline interpolated
* volume ramps and ducks.
*/
public static final int INTERPOLATOR_TYPE_CUBIC_MONOTONIC = 3;
// These values must match the native VolumeShaper::Configuration::InterpolatorType
@hide
/** @hide */
@IntDef({
OPTION_FLAG_VOLUME_IN_DBFS,
OPTION_FLAG_CLOCK_TIME,
})
@Retention(RetentionPolicy.SOURCE)
public @interface OptionFlag {}
@hide
Use a dB full scale volume range for the volume curve.
The volume scale is typically from 0.f to 1.f on a linear scale;
this option changes to -inf to 0.f on a db full scale,
where 0.f is equivalent to a scale of 1.f.
/**
* @hide
* Use a dB full scale volume range for the volume curve.
*<p>
* The volume scale is typically from 0.f to 1.f on a linear scale;
* this option changes to -inf to 0.f on a db full scale,
* where 0.f is equivalent to a scale of 1.f.
*/
public static final int OPTION_FLAG_VOLUME_IN_DBFS = (1 << 0);
@hide
Use clock time instead of media time.
The default implementation of VolumeShaper
is to apply volume changes by the media time of the player. Hence, the VolumeShaper
will speed or slow down to match player changes of playback rate, pause, or resume.
The OPTION_FLAG_CLOCK_TIME
option allows the VolumeShaper
progress to be determined by clock time instead of media time.
/**
* @hide
* Use clock time instead of media time.
*<p>
* The default implementation of {@code VolumeShaper} is to apply
* volume changes by the media time of the player.
* Hence, the {@code VolumeShaper} will speed or slow down to
* match player changes of playback rate, pause, or resume.
*<p>
* The {@code OPTION_FLAG_CLOCK_TIME} option allows the {@code VolumeShaper}
* progress to be determined by clock time instead of media time.
*/
public static final int OPTION_FLAG_CLOCK_TIME = (1 << 1);
private static final int OPTION_FLAG_PUBLIC_ALL =
OPTION_FLAG_VOLUME_IN_DBFS | OPTION_FLAG_CLOCK_TIME;
A one second linear ramp from silence to full volume. Use reflectTimes.reflectTimes()
or invertVolumes.invertVolumes()
to generate the matching linear duck. /**
* A one second linear ramp from silence to full volume.
* Use {@link VolumeShaper.Builder#reflectTimes()}
* or {@link VolumeShaper.Builder#invertVolumes()} to generate
* the matching linear duck.
*/
public static final Configuration LINEAR_RAMP = new VolumeShaper.Configuration.Builder()
.setInterpolatorType(INTERPOLATOR_TYPE_LINEAR)
.setCurve(new float[] {0.f, 1.f} /* times */,
new float[] {0.f, 1.f} /* volumes */)
.setDuration(1000)
.build();
A one second cubic ramp from silence to full volume. Use reflectTimes.reflectTimes()
or invertVolumes.invertVolumes()
to generate the matching cubic duck. /**
* A one second cubic ramp from silence to full volume.
* Use {@link VolumeShaper.Builder#reflectTimes()}
* or {@link VolumeShaper.Builder#invertVolumes()} to generate
* the matching cubic duck.
*/
public static final Configuration CUBIC_RAMP = new VolumeShaper.Configuration.Builder()
.setInterpolatorType(INTERPOLATOR_TYPE_CUBIC)
.setCurve(new float[] {0.f, 1.f} /* times */,
new float[] {0.f, 1.f} /* volumes */)
.setDuration(1000)
.build();
A one second sine curve from silence to full volume for energy preserving cross fades. Use reflectTimes.reflectTimes()
to generate the matching cosine duck. /**
* A one second sine curve
* from silence to full volume for energy preserving cross fades.
* Use {@link VolumeShaper.Builder#reflectTimes()} to generate
* the matching cosine duck.
*/
public static final Configuration SINE_RAMP;
A one second sine-squared s-curve ramp from silence to full volume. Use reflectTimes.reflectTimes()
or invertVolumes.invertVolumes()
to generate the matching sine-squared s-curve duck. /**
* A one second sine-squared s-curve ramp
* from silence to full volume.
* Use {@link VolumeShaper.Builder#reflectTimes()}
* or {@link VolumeShaper.Builder#invertVolumes()} to generate
* the matching sine-squared s-curve duck.
*/
public static final Configuration SCURVE_RAMP;
static {
final int POINTS = MAXIMUM_CURVE_POINTS;
final float times[] = new float[POINTS];
final float sines[] = new float[POINTS];
final float scurve[] = new float[POINTS];
for (int i = 0; i < POINTS; ++i) {
times[i] = (float)i / (POINTS - 1);
final float sine = (float)Math.sin(times[i] * Math.PI / 2.);
sines[i] = sine;
scurve[i] = sine * sine;
}
SINE_RAMP = new VolumeShaper.Configuration.Builder()
.setInterpolatorType(INTERPOLATOR_TYPE_CUBIC)
.setCurve(times, sines)
.setDuration(1000)
.build();
SCURVE_RAMP = new VolumeShaper.Configuration.Builder()
.setInterpolatorType(INTERPOLATOR_TYPE_CUBIC)
.setCurve(times, scurve)
.setDuration(1000)
.build();
}
/*
* member variables - these are all final
*/
// type of VolumeShaper
private final int mType;
// valid when mType is TYPE_ID
private final int mId;
// valid when mType is TYPE_SCALE
private final int mOptionFlags;
private final double mDurationMs;
private final int mInterpolatorType;
private final float[] mTimes;
private final float[] mVolumes;
@Override
public String toString() {
return "VolumeShaper.Configuration{"
+ "mType = " + mType
+ ", mId = " + mId
+ (mType == TYPE_ID
? "}"
: ", mOptionFlags = 0x" + Integer.toHexString(mOptionFlags).toUpperCase()
+ ", mDurationMs = " + mDurationMs
+ ", mInterpolatorType = " + mInterpolatorType
+ ", mTimes[] = " + Arrays.toString(mTimes)
+ ", mVolumes[] = " + Arrays.toString(mVolumes)
+ "}");
}
@Override
public int hashCode() {
return mType == TYPE_ID
? Objects.hash(mType, mId)
: Objects.hash(mType, mId,
mOptionFlags, mDurationMs, mInterpolatorType,
Arrays.hashCode(mTimes), Arrays.hashCode(mVolumes));
}
@Override
public boolean equals(Object o) {
if (!(o instanceof Configuration)) return false;
if (o == this) return true;
final Configuration other = (Configuration) o;
// Note that exact floating point equality may not be guaranteed
// for a theoretically idempotent operation; for example,
// there are many cases where a + b - b != a.
return mType == other.mType
&& mId == other.mId
&& (mType == TYPE_ID
|| (mOptionFlags == other.mOptionFlags
&& mDurationMs == other.mDurationMs
&& mInterpolatorType == other.mInterpolatorType
&& Arrays.equals(mTimes, other.mTimes)
&& Arrays.equals(mVolumes, other.mVolumes)));
}
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
// this needs to match the native VolumeShaper.Configuration parceling
dest.writeInt(mType);
dest.writeInt(mId);
if (mType != TYPE_ID) {
dest.writeInt(mOptionFlags);
dest.writeDouble(mDurationMs);
// this needs to match the native Interpolator parceling
dest.writeInt(mInterpolatorType);
dest.writeFloat(0.f); // first slope (specifying for native side)
dest.writeFloat(0.f); // last slope (specifying for native side)
// mTimes and mVolumes should have the same length.
dest.writeInt(mTimes.length);
for (int i = 0; i < mTimes.length; ++i) {
dest.writeFloat(mTimes[i]);
dest.writeFloat(mVolumes[i]);
}
}
}
public static final Parcelable.Creator<VolumeShaper.Configuration> CREATOR
= new Parcelable.Creator<VolumeShaper.Configuration>() {
@Override
public VolumeShaper.Configuration createFromParcel(Parcel p) {
// this needs to match the native VolumeShaper.Configuration parceling
final int type = p.readInt();
final int id = p.readInt();
if (type == TYPE_ID) {
return new VolumeShaper.Configuration(id);
} else {
final int optionFlags = p.readInt();
final double durationMs = p.readDouble();
// this needs to match the native Interpolator parceling
final int interpolatorType = p.readInt();
final float firstSlope = p.readFloat(); // ignored on the Java side
final float lastSlope = p.readFloat(); // ignored on the Java side
final int length = p.readInt();
final float[] times = new float[length];
final float[] volumes = new float[length];
for (int i = 0; i < length; ++i) {
times[i] = p.readFloat();
volumes[i] = p.readFloat();
}
return new VolumeShaper.Configuration(
type,
id,
optionFlags,
durationMs,
interpolatorType,
times,
volumes);
}
}
@Override
public VolumeShaper.Configuration[] newArray(int size) {
return new VolumeShaper.Configuration[size];
}
};
Params: - id –
Throws: - IllegalArgumentException – if id is negative.
@hide Constructs a VolumeShaper
from an id. This is an opaque handle for controlling a VolumeShaper
that has already been sent to a player. The id
is returned from the initial setVolumeShaper()
call on success. These configurations are for native use only, they are never returned directly to the user.
/**
* @hide
* Constructs a {@code VolumeShaper} from an id.
*
* This is an opaque handle for controlling a {@code VolumeShaper} that has
* already been sent to a player. The {@code id} is returned from the
* initial {@code setVolumeShaper()} call on success.
*
* These configurations are for native use only,
* they are never returned directly to the user.
*
* @param id
* @throws IllegalArgumentException if id is negative.
*/
public Configuration(int id) {
if (id < 0) {
throw new IllegalArgumentException("negative id " + id);
}
mType = TYPE_ID;
mId = id;
mInterpolatorType = 0;
mOptionFlags = 0;
mDurationMs = 0;
mTimes = null;
mVolumes = null;
}
Direct constructor for VolumeShaper.
Use the Builder instead.
/**
* Direct constructor for VolumeShaper.
* Use the Builder instead.
*/
private Configuration(@Type int type,
int id,
@OptionFlag int optionFlags,
double durationMs,
@InterpolatorType int interpolatorType,
@NonNull float[] times,
@NonNull float[] volumes) {
mType = type;
mId = id;
mOptionFlags = optionFlags;
mDurationMs = durationMs;
mInterpolatorType = interpolatorType;
// Builder should have cloned these arrays already.
mTimes = times;
mVolumes = volumes;
}
@hide Returns the VolumeShaper
type.
/**
* @hide
* Returns the {@code VolumeShaper} type.
*/
public @Type int getType() {
return mType;
}
@hide Returns the VolumeShaper
id.
/**
* @hide
* Returns the {@code VolumeShaper} id.
*/
public int getId() {
return mId;
}
Returns the interpolator type.
/**
* Returns the interpolator type.
*/
public @InterpolatorType int getInterpolatorType() {
return mInterpolatorType;
}
@hide
Returns the option flags
/**
* @hide
* Returns the option flags
*/
public @OptionFlag int getOptionFlags() {
return mOptionFlags & OPTION_FLAG_PUBLIC_ALL;
}
/* package */ @OptionFlag int getAllOptionFlags() {
return mOptionFlags;
}
Returns the duration of the volume shape in milliseconds.
/**
* Returns the duration of the volume shape in milliseconds.
*/
public long getDuration() {
// casting is safe here as the duration was set as a long in the Builder
return (long) mDurationMs;
}
Returns the times (x) coordinate array of the volume curve points.
/**
* Returns the times (x) coordinate array of the volume curve points.
*/
public float[] getTimes() {
return mTimes;
}
Returns the volumes (y) coordinate array of the volume curve points.
/**
* Returns the volumes (y) coordinate array of the volume curve points.
*/
public float[] getVolumes() {
return mVolumes;
}
Checks the validity of times and volumes point representation. times[]
and volumes[]
are two arrays representing points for the volume curve. Note that times[]
and volumes[]
are explicitly checked against null here to provide the proper error string - those are legitimate arguments to this method. Params: - times – the x coordinates for the points,
must be between 0.f and 1.f and be monotonic.
- volumes – the y coordinates for the points,
must be between 0.f and 1.f for linear and
must be no greater than 0.f for log (dBFS).
- log – set to true if the scale is logarithmic.
Returns: null if no error, or the reason in a String
for an error.
/**
* Checks the validity of times and volumes point representation.
*
* {@code times[]} and {@code volumes[]} are two arrays representing points
* for the volume curve.
*
* Note that {@code times[]} and {@code volumes[]} are explicitly checked against
* null here to provide the proper error string - those are legitimate
* arguments to this method.
*
* @param times the x coordinates for the points,
* must be between 0.f and 1.f and be monotonic.
* @param volumes the y coordinates for the points,
* must be between 0.f and 1.f for linear and
* must be no greater than 0.f for log (dBFS).
* @param log set to true if the scale is logarithmic.
* @return null if no error, or the reason in a {@code String} for an error.
*/
private static @Nullable String checkCurveForErrors(
@Nullable float[] times, @Nullable float[] volumes, boolean log) {
if (times == null) {
return "times array must be non-null";
} else if (volumes == null) {
return "volumes array must be non-null";
} else if (times.length != volumes.length) {
return "array length must match";
} else if (times.length < 2) {
return "array length must be at least 2";
} else if (times.length > MAXIMUM_CURVE_POINTS) {
return "array length must be no larger than " + MAXIMUM_CURVE_POINTS;
} else if (times[0] != 0.f) {
return "times must start at 0.f";
} else if (times[times.length - 1] != 1.f) {
return "times must end at 1.f";
}
// validate points along the curve
for (int i = 1; i < times.length; ++i) {
if (!(times[i] > times[i - 1]) /* handle nan */) {
return "times not monotonic increasing, check index " + i;
}
}
if (log) {
for (int i = 0; i < volumes.length; ++i) {
if (!(volumes[i] <= 0.f) /* handle nan */) {
return "volumes for log scale cannot be positive, "
+ "check index " + i;
}
}
} else {
for (int i = 0; i < volumes.length; ++i) {
if (!(volumes[i] >= 0.f) || !(volumes[i] <= 1.f) /* handle nan */) {
return "volumes for linear scale must be between 0.f and 1.f, "
+ "check index " + i;
}
}
}
return null; // no errors
}
private static void checkCurveForErrorsAndThrowException(
@Nullable float[] times, @Nullable float[] volumes, boolean log, boolean ise) {
final String error = checkCurveForErrors(times, volumes, log);
if (error != null) {
if (ise) {
throw new IllegalStateException(error);
} else {
throw new IllegalArgumentException(error);
}
}
}
private static void checkValidVolumeAndThrowException(float volume, boolean log) {
if (log) {
if (!(volume <= 0.f) /* handle nan */) {
throw new IllegalArgumentException("dbfs volume must be 0.f or less");
}
} else {
if (!(volume >= 0.f) || !(volume <= 1.f) /* handle nan */) {
throw new IllegalArgumentException("volume must be >= 0.f and <= 1.f");
}
}
}
private static void clampVolume(float[] volumes, boolean log) {
if (log) {
for (int i = 0; i < volumes.length; ++i) {
if (!(volumes[i] <= 0.f) /* handle nan */) {
volumes[i] = 0.f;
}
}
} else {
for (int i = 0; i < volumes.length; ++i) {
if (!(volumes[i] >= 0.f) /* handle nan */) {
volumes[i] = 0.f;
} else if (!(volumes[i] <= 1.f)) {
volumes[i] = 1.f;
}
}
}
}
Builder class for a Configuration
object. Here is an example where Builder
is used to define the Configuration
.
VolumeShaper.Configuration LINEAR_RAMP =
new VolumeShaper.Configuration.Builder()
.setInterpolatorType(VolumeShaper.Configuration.INTERPOLATOR_TYPE_LINEAR)
.setCurve(new float[] { 0.f, 1.f }, // times
new float[] { 0.f, 1.f }) // volumes
.setDuration(1000)
.build();
/**
* Builder class for a {@link VolumeShaper.Configuration} object.
* <p> Here is an example where {@code Builder} is used to define the
* {@link VolumeShaper.Configuration}.
*
* <pre class="prettyprint">
* VolumeShaper.Configuration LINEAR_RAMP =
* new VolumeShaper.Configuration.Builder()
* .setInterpolatorType(VolumeShaper.Configuration.INTERPOLATOR_TYPE_LINEAR)
* .setCurve(new float[] { 0.f, 1.f }, // times
* new float[] { 0.f, 1.f }) // volumes
* .setDuration(1000)
* .build();
* </pre>
* <p>
*/
public static final class Builder {
private int mType = TYPE_SCALE;
private int mId = -1; // invalid
private int mInterpolatorType = INTERPOLATOR_TYPE_CUBIC;
private int mOptionFlags = OPTION_FLAG_CLOCK_TIME;
private double mDurationMs = 1000.;
private float[] mTimes = null;
private float[] mVolumes = null;
Constructs a new Builder
with the defaults. /**
* Constructs a new {@code Builder} with the defaults.
*/
public Builder() {
}
Constructs a new Builder
with settings copied from a given VolumeShaper.Configuration
. Params: - configuration – prototypical configuration which will be reused in the new
Builder
.
/**
* Constructs a new {@code Builder} with settings
* copied from a given {@code VolumeShaper.Configuration}.
* @param configuration prototypical configuration
* which will be reused in the new {@code Builder}.
*/
public Builder(@NonNull Configuration configuration) {
mType = configuration.getType();
mId = configuration.getId();
mOptionFlags = configuration.getAllOptionFlags();
mInterpolatorType = configuration.getInterpolatorType();
mDurationMs = configuration.getDuration();
mTimes = configuration.getTimes().clone();
mVolumes = configuration.getVolumes().clone();
}
Params: - id – the
id
to set. If non-negative, then it is used. If -1, then the system is expected to assign one.
Throws: - IllegalArgumentException – if
id
< -1.
@hide Set the id
for system defined shapers. Returns: the same Builder
instance.
/**
* @hide
* Set the {@code id} for system defined shapers.
* @param id the {@code id} to set. If non-negative, then it is used.
* If -1, then the system is expected to assign one.
* @return the same {@code Builder} instance.
* @throws IllegalArgumentException if {@code id} < -1.
*/
public @NonNull Builder setId(int id) {
if (id < -1) {
throw new IllegalArgumentException("invalid id: " + id);
}
mId = id;
return this;
}
Sets the interpolator type. If omitted the default interpolator type is Configuration.INTERPOLATOR_TYPE_CUBIC
. Params: - interpolatorType – method of interpolation used for the volume curve. One of
Configuration.INTERPOLATOR_TYPE_STEP
, Configuration.INTERPOLATOR_TYPE_LINEAR
, Configuration.INTERPOLATOR_TYPE_CUBIC
, Configuration.INTERPOLATOR_TYPE_CUBIC_MONOTONIC
.
Throws: - IllegalArgumentException – if
interpolatorType
is not valid.
Returns: the same Builder
instance.
/**
* Sets the interpolator type.
*
* If omitted the default interpolator type is {@link #INTERPOLATOR_TYPE_CUBIC}.
*
* @param interpolatorType method of interpolation used for the volume curve.
* One of {@link #INTERPOLATOR_TYPE_STEP},
* {@link #INTERPOLATOR_TYPE_LINEAR},
* {@link #INTERPOLATOR_TYPE_CUBIC},
* {@link #INTERPOLATOR_TYPE_CUBIC_MONOTONIC}.
* @return the same {@code Builder} instance.
* @throws IllegalArgumentException if {@code interpolatorType} is not valid.
*/
public @NonNull Builder setInterpolatorType(@InterpolatorType int interpolatorType) {
switch (interpolatorType) {
case INTERPOLATOR_TYPE_STEP:
case INTERPOLATOR_TYPE_LINEAR:
case INTERPOLATOR_TYPE_CUBIC:
case INTERPOLATOR_TYPE_CUBIC_MONOTONIC:
mInterpolatorType = interpolatorType;
break;
default:
throw new IllegalArgumentException("invalid interpolatorType: "
+ interpolatorType);
}
return this;
}
Params: - optionFlags – new value to replace the old
optionFlags
.
Throws: - IllegalArgumentException – if flag is not recognized.
@hide Sets the optional flags If omitted, flags are 0. If Configuration.OPTION_FLAG_VOLUME_IN_DBFS
has changed the volume curve needs to be set again as the acceptable volume domain has changed. Returns: the same Builder
instance.
/**
* @hide
* Sets the optional flags
*
* If omitted, flags are 0. If {@link #OPTION_FLAG_VOLUME_IN_DBFS} has
* changed the volume curve needs to be set again as the acceptable
* volume domain has changed.
*
* @param optionFlags new value to replace the old {@code optionFlags}.
* @return the same {@code Builder} instance.
* @throws IllegalArgumentException if flag is not recognized.
*/
@TestApi
public @NonNull Builder setOptionFlags(@OptionFlag int optionFlags) {
if ((optionFlags & ~OPTION_FLAG_PUBLIC_ALL) != 0) {
throw new IllegalArgumentException("invalid bits in flag: " + optionFlags);
}
mOptionFlags = mOptionFlags & ~OPTION_FLAG_PUBLIC_ALL | optionFlags;
return this;
}
Sets the VolumeShaper
duration in milliseconds. If omitted, the default duration is 1 second. Params: - durationMillis –
Throws: - IllegalArgumentException – if
durationMillis
is not strictly positive.
Returns: the same Builder
instance.
/**
* Sets the {@code VolumeShaper} duration in milliseconds.
*
* If omitted, the default duration is 1 second.
*
* @param durationMillis
* @return the same {@code Builder} instance.
* @throws IllegalArgumentException if {@code durationMillis}
* is not strictly positive.
*/
public @NonNull Builder setDuration(long durationMillis) {
if (durationMillis <= 0) {
throw new IllegalArgumentException(
"duration: " + durationMillis + " not positive");
}
mDurationMs = (double) durationMillis;
return this;
}
Sets the volume curve. The volume curve is represented by a set of control points given by two float arrays of equal length, one representing the time (x) coordinates and one corresponding to the volume (y) coordinates. The length must be at least 2 and no greater than Configuration.getMaximumCurvePoints()
.
The volume curve is normalized as follows:
time (x) coordinates should be monotonically increasing, from 0.f to 1.f;
volume (y) coordinates must be within 0.f to 1.f.
The time scale is set by setDuration
.
Params: - times – an array of float values representing
the time line of the volume curve.
- volumes – an array of float values representing
the amplitude of the volume curve.
Throws: - IllegalArgumentException – if
times
or volumes
is invalid.
Returns: the same Builder
instance.
/**
* Sets the volume curve.
*
* The volume curve is represented by a set of control points given by
* two float arrays of equal length,
* one representing the time (x) coordinates
* and one corresponding to the volume (y) coordinates.
* The length must be at least 2
* and no greater than {@link VolumeShaper.Configuration#getMaximumCurvePoints()}.
* <p>
* The volume curve is normalized as follows:
* time (x) coordinates should be monotonically increasing, from 0.f to 1.f;
* volume (y) coordinates must be within 0.f to 1.f.
* <p>
* The time scale is set by {@link #setDuration}.
* <p>
* @param times an array of float values representing
* the time line of the volume curve.
* @param volumes an array of float values representing
* the amplitude of the volume curve.
* @return the same {@code Builder} instance.
* @throws IllegalArgumentException if {@code times} or {@code volumes} is invalid.
*/
/* Note: volume (y) coordinates must be non-positive for log scaling,
* if {@link VolumeShaper.Configuration#OPTION_FLAG_VOLUME_IN_DBFS} is set.
*/
public @NonNull Builder setCurve(@NonNull float[] times, @NonNull float[] volumes) {
final boolean log = (mOptionFlags & OPTION_FLAG_VOLUME_IN_DBFS) != 0;
checkCurveForErrorsAndThrowException(times, volumes, log, false /* ise */);
mTimes = times.clone();
mVolumes = volumes.clone();
return this;
}
Reflects the volume curve so that
the shaper changes volume from the end
to the start.
Throws: - IllegalStateException – if curve has not been set.
Returns: the same Builder
instance.
/**
* Reflects the volume curve so that
* the shaper changes volume from the end
* to the start.
*
* @return the same {@code Builder} instance.
* @throws IllegalStateException if curve has not been set.
*/
public @NonNull Builder reflectTimes() {
final boolean log = (mOptionFlags & OPTION_FLAG_VOLUME_IN_DBFS) != 0;
checkCurveForErrorsAndThrowException(mTimes, mVolumes, log, true /* ise */);
int i;
for (i = 0; i < mTimes.length / 2; ++i) {
float temp = mTimes[i];
mTimes[i] = 1.f - mTimes[mTimes.length - 1 - i];
mTimes[mTimes.length - 1 - i] = 1.f - temp;
temp = mVolumes[i];
mVolumes[i] = mVolumes[mVolumes.length - 1 - i];
mVolumes[mVolumes.length - 1 - i] = temp;
}
if ((mTimes.length & 1) != 0) {
mTimes[i] = 1.f - mTimes[i];
}
return this;
}
Inverts the volume curve so that the max volume
becomes the min volume and vice versa.
Throws: - IllegalStateException – if curve has not been set.
Returns: the same Builder
instance.
/**
* Inverts the volume curve so that the max volume
* becomes the min volume and vice versa.
*
* @return the same {@code Builder} instance.
* @throws IllegalStateException if curve has not been set.
*/
public @NonNull Builder invertVolumes() {
final boolean log = (mOptionFlags & OPTION_FLAG_VOLUME_IN_DBFS) != 0;
checkCurveForErrorsAndThrowException(mTimes, mVolumes, log, true /* ise */);
float min = mVolumes[0];
float max = mVolumes[0];
for (int i = 1; i < mVolumes.length; ++i) {
if (mVolumes[i] < min) {
min = mVolumes[i];
} else if (mVolumes[i] > max) {
max = mVolumes[i];
}
}
final float maxmin = max + min;
for (int i = 0; i < mVolumes.length; ++i) {
mVolumes[i] = maxmin - mVolumes[i];
}
return this;
}
Scale the curve end volume to a target value.
Keeps the start volume the same.
This works best if the volume curve is monotonic.
Params: - volume – the target end volume to use.
Throws: - IllegalArgumentException – if
volume
is not valid. - IllegalStateException – if curve has not been set.
Returns: the same Builder
instance.
/**
* Scale the curve end volume to a target value.
*
* Keeps the start volume the same.
* This works best if the volume curve is monotonic.
*
* @param volume the target end volume to use.
* @return the same {@code Builder} instance.
* @throws IllegalArgumentException if {@code volume} is not valid.
* @throws IllegalStateException if curve has not been set.
*/
public @NonNull Builder scaleToEndVolume(float volume) {
final boolean log = (mOptionFlags & OPTION_FLAG_VOLUME_IN_DBFS) != 0;
checkCurveForErrorsAndThrowException(mTimes, mVolumes, log, true /* ise */);
checkValidVolumeAndThrowException(volume, log);
final float startVolume = mVolumes[0];
final float endVolume = mVolumes[mVolumes.length - 1];
if (endVolume == startVolume) {
// match with linear ramp
final float offset = volume - startVolume;
for (int i = 0; i < mVolumes.length; ++i) {
mVolumes[i] = mVolumes[i] + offset * mTimes[i];
}
} else {
// scale
final float scale = (volume - startVolume) / (endVolume - startVolume);
for (int i = 0; i < mVolumes.length; ++i) {
mVolumes[i] = scale * (mVolumes[i] - startVolume) + startVolume;
}
}
clampVolume(mVolumes, log);
return this;
}
Scale the curve start volume to a target value.
Keeps the end volume the same.
This works best if the volume curve is monotonic.
Params: - volume – the target start volume to use.
Throws: - IllegalArgumentException – if
volume
is not valid. - IllegalStateException – if curve has not been set.
Returns: the same Builder
instance.
/**
* Scale the curve start volume to a target value.
*
* Keeps the end volume the same.
* This works best if the volume curve is monotonic.
*
* @param volume the target start volume to use.
* @return the same {@code Builder} instance.
* @throws IllegalArgumentException if {@code volume} is not valid.
* @throws IllegalStateException if curve has not been set.
*/
public @NonNull Builder scaleToStartVolume(float volume) {
final boolean log = (mOptionFlags & OPTION_FLAG_VOLUME_IN_DBFS) != 0;
checkCurveForErrorsAndThrowException(mTimes, mVolumes, log, true /* ise */);
checkValidVolumeAndThrowException(volume, log);
final float startVolume = mVolumes[0];
final float endVolume = mVolumes[mVolumes.length - 1];
if (endVolume == startVolume) {
// match with linear ramp
final float offset = volume - startVolume;
for (int i = 0; i < mVolumes.length; ++i) {
mVolumes[i] = mVolumes[i] + offset * (1.f - mTimes[i]);
}
} else {
final float scale = (volume - endVolume) / (startVolume - endVolume);
for (int i = 0; i < mVolumes.length; ++i) {
mVolumes[i] = scale * (mVolumes[i] - endVolume) + endVolume;
}
}
clampVolume(mVolumes, log);
return this;
}
Builds a new VolumeShaper
object. Throws: - IllegalStateException – if curve is not properly set.
Returns: a new VolumeShaper
object.
/**
* Builds a new {@link VolumeShaper} object.
*
* @return a new {@link VolumeShaper} object.
* @throws IllegalStateException if curve is not properly set.
*/
public @NonNull Configuration build() {
final boolean log = (mOptionFlags & OPTION_FLAG_VOLUME_IN_DBFS) != 0;
checkCurveForErrorsAndThrowException(mTimes, mVolumes, log, true /* ise */);
return new Configuration(mType, mId, mOptionFlags, mDurationMs,
mInterpolatorType, mTimes, mVolumes);
}
} // Configuration.Builder
} // Configuration
The VolumeShaper.Operation
class is used to specify operations to the VolumeShaper
that affect the volume change. /**
* The {@code VolumeShaper.Operation} class is used to specify operations
* to the {@code VolumeShaper} that affect the volume change.
*/
public static final class Operation implements Parcelable {
Forward playback from current volume time position. At the end of the VolumeShaper
curve, the last volume value persists. /**
* Forward playback from current volume time position.
* At the end of the {@code VolumeShaper} curve,
* the last volume value persists.
*/
public static final Operation PLAY =
new VolumeShaper.Operation.Builder()
.build();
Reverse playback from current volume time position. When the position reaches the start of the VolumeShaper
curve, the first volume value persists. /**
* Reverse playback from current volume time position.
* When the position reaches the start of the {@code VolumeShaper} curve,
* the first volume value persists.
*/
public static final Operation REVERSE =
new VolumeShaper.Operation.Builder()
.reverse()
.build();
// No user serviceable parts below.
// These flags must match the native VolumeShaper::Operation::Flag
@hide
/** @hide */
@IntDef({
FLAG_NONE,
FLAG_REVERSE,
FLAG_TERMINATE,
FLAG_JOIN,
FLAG_DEFER,
})
@Retention(RetentionPolicy.SOURCE)
public @interface Flag {}
No special VolumeShaper
operation. /**
* No special {@code VolumeShaper} operation.
*/
private static final int FLAG_NONE = 0;
Reverse the VolumeShaper
progress. Reverses the VolumeShaper
curve from its current position. If the VolumeShaper
curve has not started, it automatically is considered finished. /**
* Reverse the {@code VolumeShaper} progress.
*
* Reverses the {@code VolumeShaper} curve from its current
* position. If the {@code VolumeShaper} curve has not started,
* it automatically is considered finished.
*/
private static final int FLAG_REVERSE = 1 << 0;
Terminate the existing VolumeShaper
. This flag is generally used by itself; it takes precedence over all other flags. /**
* Terminate the existing {@code VolumeShaper}.
* This flag is generally used by itself;
* it takes precedence over all other flags.
*/
private static final int FLAG_TERMINATE = 1 << 1;
Attempt to join as best as possible to the previous VolumeShaper
. This requires the previous VolumeShaper
to be active and setReplaceId
to be set. /**
* Attempt to join as best as possible to the previous {@code VolumeShaper}.
* This requires the previous {@code VolumeShaper} to be active and
* {@link #setReplaceId} to be set.
*/
private static final int FLAG_JOIN = 1 << 2;
Defer playback until next operation is sent. This is used when starting a VolumeShaper
effect. /**
* Defer playback until next operation is sent. This is used
* when starting a {@code VolumeShaper} effect.
*/
private static final int FLAG_DEFER = 1 << 3;
Use the id specified in the configuration, creating VolumeShaper
as needed; the configuration should be TYPE_SCALE. /**
* Use the id specified in the configuration, creating
* {@code VolumeShaper} as needed; the configuration should be
* TYPE_SCALE.
*/
private static final int FLAG_CREATE_IF_NEEDED = 1 << 4;
private static final int FLAG_PUBLIC_ALL = FLAG_REVERSE | FLAG_TERMINATE;
private final int mFlags;
private final int mReplaceId;
private final float mXOffset;
@Override
public String toString() {
return "VolumeShaper.Operation{"
+ "mFlags = 0x" + Integer.toHexString(mFlags).toUpperCase()
+ ", mReplaceId = " + mReplaceId
+ ", mXOffset = " + mXOffset
+ "}";
}
@Override
public int hashCode() {
return Objects.hash(mFlags, mReplaceId, mXOffset);
}
@Override
public boolean equals(Object o) {
if (!(o instanceof Operation)) return false;
if (o == this) return true;
final Operation other = (Operation) o;
return mFlags == other.mFlags
&& mReplaceId == other.mReplaceId
&& Float.compare(mXOffset, other.mXOffset) == 0;
}
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
// this needs to match the native VolumeShaper.Operation parceling
dest.writeInt(mFlags);
dest.writeInt(mReplaceId);
dest.writeFloat(mXOffset);
}
public static final Parcelable.Creator<VolumeShaper.Operation> CREATOR
= new Parcelable.Creator<VolumeShaper.Operation>() {
@Override
public VolumeShaper.Operation createFromParcel(Parcel p) {
// this needs to match the native VolumeShaper.Operation parceling
final int flags = p.readInt();
final int replaceId = p.readInt();
final float xOffset = p.readFloat();
return new VolumeShaper.Operation(
flags
, replaceId
, xOffset);
}
@Override
public VolumeShaper.Operation[] newArray(int size) {
return new VolumeShaper.Operation[size];
}
};
private Operation(@Flag int flags, int replaceId, float xOffset) {
mFlags = flags;
mReplaceId = replaceId;
mXOffset = xOffset;
}
@hide Builder
class for Operation
object. Not for public use.
/**
* @hide
* {@code Builder} class for {@link VolumeShaper.Operation} object.
*
* Not for public use.
*/
public static final class Builder {
int mFlags;
int mReplaceId;
float mXOffset;
Constructs a new Builder
with the defaults. /**
* Constructs a new {@code Builder} with the defaults.
*/
public Builder() {
mFlags = 0;
mReplaceId = -1;
mXOffset = Float.NaN;
}
Constructs a new Builder
from a given VolumeShaper.Operation
Params: - operation – the
VolumeShaper.operation
whose data will be reused in the new Builder
.
/**
* Constructs a new {@code Builder} from a given {@code VolumeShaper.Operation}
* @param operation the {@code VolumeShaper.operation} whose data will be
* reused in the new {@code Builder}.
*/
public Builder(@NonNull VolumeShaper.Operation operation) {
mReplaceId = operation.mReplaceId;
mFlags = operation.mFlags;
mXOffset = operation.mXOffset;
}
Replaces the previous VolumeShaper
specified by id
. The VolumeShaper
specified by the id
is removed if it exists. The configuration should be TYPE_SCALE. Params: - id – the
id
of the previous VolumeShaper
. - join – if true, match the volume of the previous shaper to the start volume of the new
VolumeShaper
.
Returns: the same Builder
instance.
/**
* Replaces the previous {@code VolumeShaper} specified by {@code id}.
*
* The {@code VolumeShaper} specified by the {@code id} is removed
* if it exists. The configuration should be TYPE_SCALE.
*
* @param id the {@code id} of the previous {@code VolumeShaper}.
* @param join if true, match the volume of the previous
* shaper to the start volume of the new {@code VolumeShaper}.
* @return the same {@code Builder} instance.
*/
public @NonNull Builder replace(int id, boolean join) {
mReplaceId = id;
if (join) {
mFlags |= FLAG_JOIN;
} else {
mFlags &= ~FLAG_JOIN;
}
return this;
}
Defers all operations.
Returns: the same Builder
instance.
/**
* Defers all operations.
* @return the same {@code Builder} instance.
*/
public @NonNull Builder defer() {
mFlags |= FLAG_DEFER;
return this;
}
Terminates the VolumeShaper
. Do not call directly, use VolumeShaper.close()
. Returns: the same Builder
instance.
/**
* Terminates the {@code VolumeShaper}.
*
* Do not call directly, use {@link VolumeShaper#close()}.
* @return the same {@code Builder} instance.
*/
public @NonNull Builder terminate() {
mFlags |= FLAG_TERMINATE;
return this;
}
Reverses direction.
Returns: the same Builder
instance.
/**
* Reverses direction.
* @return the same {@code Builder} instance.
*/
public @NonNull Builder reverse() {
mFlags ^= FLAG_REVERSE;
return this;
}
Use the id specified in the configuration, creating VolumeShaper
only as needed; the configuration should be TYPE_SCALE. If the VolumeShaper
with the same id already exists then the operation has no effect. Returns: the same Builder
instance.
/**
* Use the id specified in the configuration, creating
* {@code VolumeShaper} only as needed; the configuration should be
* TYPE_SCALE.
*
* If the {@code VolumeShaper} with the same id already exists
* then the operation has no effect.
*
* @return the same {@code Builder} instance.
*/
public @NonNull Builder createIfNeeded() {
mFlags |= FLAG_CREATE_IF_NEEDED;
return this;
}
Sets the xOffset
to use for the VolumeShaper
. The xOffset
is the position on the volume curve, and setting takes effect when the VolumeShaper
is used next. Params: - xOffset – a value between (or equal to) 0.f and 1.f, or Float.NaN to ignore.
Throws: - IllegalArgumentException – if
xOffset
is not between 0.f and 1.f, or a Float.NaN.
Returns: the same Builder
instance.
/**
* Sets the {@code xOffset} to use for the {@code VolumeShaper}.
*
* The {@code xOffset} is the position on the volume curve,
* and setting takes effect when the {@code VolumeShaper} is used next.
*
* @param xOffset a value between (or equal to) 0.f and 1.f, or Float.NaN to ignore.
* @return the same {@code Builder} instance.
* @throws IllegalArgumentException if {@code xOffset} is not between 0.f and 1.f,
* or a Float.NaN.
*/
public @NonNull Builder setXOffset(float xOffset) {
if (xOffset < -0.f) {
throw new IllegalArgumentException("Negative xOffset not allowed");
} else if (xOffset > 1.f) {
throw new IllegalArgumentException("xOffset > 1.f not allowed");
}
// Float.NaN passes through
mXOffset = xOffset;
return this;
}
Sets the operation flag. Do not call this directly but one of the
other builder methods.
Params: - flags – new value for
flags
, consisting of ORed flags.
Throws: - IllegalArgumentException – if
flags
contains invalid set bits.
Returns: the same Builder
instance.
/**
* Sets the operation flag. Do not call this directly but one of the
* other builder methods.
*
* @param flags new value for {@code flags}, consisting of ORed flags.
* @return the same {@code Builder} instance.
* @throws IllegalArgumentException if {@code flags} contains invalid set bits.
*/
private @NonNull Builder setFlags(@Flag int flags) {
if ((flags & ~FLAG_PUBLIC_ALL) != 0) {
throw new IllegalArgumentException("flag has unknown bits set: " + flags);
}
mFlags = mFlags & ~FLAG_PUBLIC_ALL | flags;
return this;
}
Builds a new Operation
object. Returns: a new VolumeShaper.Operation
object
/**
* Builds a new {@link VolumeShaper.Operation} object.
*
* @return a new {@code VolumeShaper.Operation} object
*/
public @NonNull Operation build() {
return new Operation(mFlags, mReplaceId, mXOffset);
}
} // Operation.Builder
} // Operation
@hide VolumeShaper.State
represents the current progress of the VolumeShaper
. Not for public use.
/**
* @hide
* {@code VolumeShaper.State} represents the current progress
* of the {@code VolumeShaper}.
*
* Not for public use.
*/
public static final class State implements Parcelable {
private float mVolume;
private float mXOffset;
@Override
public String toString() {
return "VolumeShaper.State{"
+ "mVolume = " + mVolume
+ ", mXOffset = " + mXOffset
+ "}";
}
@Override
public int hashCode() {
return Objects.hash(mVolume, mXOffset);
}
@Override
public boolean equals(Object o) {
if (!(o instanceof State)) return false;
if (o == this) return true;
final State other = (State) o;
return mVolume == other.mVolume
&& mXOffset == other.mXOffset;
}
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeFloat(mVolume);
dest.writeFloat(mXOffset);
}
public static final Parcelable.Creator<VolumeShaper.State> CREATOR
= new Parcelable.Creator<VolumeShaper.State>() {
@Override
public VolumeShaper.State createFromParcel(Parcel p) {
return new VolumeShaper.State(
p.readFloat() // volume
, p.readFloat()); // xOffset
}
@Override
public VolumeShaper.State[] newArray(int size) {
return new VolumeShaper.State[size];
}
};
/* package */ State(float volume, float xOffset) {
mVolume = volume;
mXOffset = xOffset;
}
Gets the volume of the State
. Returns: linear volume between 0.f and 1.f.
/**
* Gets the volume of the {@link VolumeShaper.State}.
* @return linear volume between 0.f and 1.f.
*/
public float getVolume() {
return mVolume;
}
Gets the xOffset
position on the normalized curve of the State
. Returns: the curve x position between 0.f and 1.f.
/**
* Gets the {@code xOffset} position on the normalized curve
* of the {@link VolumeShaper.State}.
* @return the curve x position between 0.f and 1.f.
*/
public float getXOffset() {
return mXOffset;
}
} // State
}