mirror of
https://github.com/samsonjs/media.git
synced 2026-04-27 15:07:40 +00:00
remove pitch field from PlaybackParameters
PiperOrigin-RevId: 294936851
This commit is contained in:
parent
a1f666cb9d
commit
68398b708e
10 changed files with 20 additions and 98 deletions
|
|
@ -132,11 +132,6 @@ public final class MediaSessionConnector {
|
||||||
* PlaybackParameters#speed}.
|
* PlaybackParameters#speed}.
|
||||||
*/
|
*/
|
||||||
public static final String EXTRAS_SPEED = "EXO_SPEED";
|
public static final String EXTRAS_SPEED = "EXO_SPEED";
|
||||||
/**
|
|
||||||
* The name of the {@link PlaybackStateCompat} float extra with the value of {@link
|
|
||||||
* PlaybackParameters#pitch}.
|
|
||||||
*/
|
|
||||||
public static final String EXTRAS_PITCH = "EXO_PITCH";
|
|
||||||
|
|
||||||
private static final long BASE_PLAYBACK_ACTIONS =
|
private static final long BASE_PLAYBACK_ACTIONS =
|
||||||
PlaybackStateCompat.ACTION_PLAY_PAUSE
|
PlaybackStateCompat.ACTION_PLAY_PAUSE
|
||||||
|
|
@ -773,7 +768,6 @@ public final class MediaSessionConnector {
|
||||||
: MediaSessionCompat.QueueItem.UNKNOWN_ID;
|
: MediaSessionCompat.QueueItem.UNKNOWN_ID;
|
||||||
PlaybackParameters playbackParameters = player.getPlaybackParameters();
|
PlaybackParameters playbackParameters = player.getPlaybackParameters();
|
||||||
extras.putFloat(EXTRAS_SPEED, playbackParameters.speed);
|
extras.putFloat(EXTRAS_SPEED, playbackParameters.speed);
|
||||||
extras.putFloat(EXTRAS_PITCH, playbackParameters.pitch);
|
|
||||||
float sessionPlaybackSpeed = player.isPlaying() ? playbackParameters.speed : 0f;
|
float sessionPlaybackSpeed = player.isPlaying() ? playbackParameters.speed : 0f;
|
||||||
builder
|
builder
|
||||||
.setActions(buildPrepareActions() | buildPlaybackActions(player))
|
.setActions(buildPrepareActions() | buildPlaybackActions(player))
|
||||||
|
|
|
||||||
|
|
@ -23,18 +23,12 @@ import com.google.android.exoplayer2.util.Assertions;
|
||||||
*/
|
*/
|
||||||
public final class PlaybackParameters {
|
public final class PlaybackParameters {
|
||||||
|
|
||||||
/**
|
/** The default playback parameters: real-time playback with no silence skipping. */
|
||||||
* The default playback parameters: real-time playback with no pitch modification or silence
|
|
||||||
* skipping.
|
|
||||||
*/
|
|
||||||
public static final PlaybackParameters DEFAULT = new PlaybackParameters(/* speed= */ 1f);
|
public static final PlaybackParameters DEFAULT = new PlaybackParameters(/* speed= */ 1f);
|
||||||
|
|
||||||
/** The factor by which playback will be sped up. */
|
/** The factor by which playback will be sped up. */
|
||||||
public final float speed;
|
public final float speed;
|
||||||
|
|
||||||
/** The factor by which the audio pitch will be scaled. */
|
|
||||||
public final float pitch;
|
|
||||||
|
|
||||||
/** Whether to skip silence in the input. */
|
/** Whether to skip silence in the input. */
|
||||||
public final boolean skipSilence;
|
public final boolean skipSilence;
|
||||||
|
|
||||||
|
|
@ -46,32 +40,19 @@ public final class PlaybackParameters {
|
||||||
* @param speed The factor by which playback will be sped up. Must be greater than zero.
|
* @param speed The factor by which playback will be sped up. Must be greater than zero.
|
||||||
*/
|
*/
|
||||||
public PlaybackParameters(float speed) {
|
public PlaybackParameters(float speed) {
|
||||||
this(speed, /* pitch= */ 1f, /* skipSilence= */ false);
|
this(speed, /* skipSilence= */ false);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates new playback parameters that set the playback speed and audio pitch scaling factor.
|
* Creates new playback parameters that set the playback speed and whether to skip silence in the
|
||||||
|
* audio stream.
|
||||||
*
|
*
|
||||||
* @param speed The factor by which playback will be sped up. Must be greater than zero.
|
* @param speed The factor by which playback will be sped up. Must be greater than zero.
|
||||||
* @param pitch The factor by which the audio pitch will be scaled. Must be greater than zero.
|
|
||||||
*/
|
|
||||||
public PlaybackParameters(float speed, float pitch) {
|
|
||||||
this(speed, pitch, /* skipSilence= */ false);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates new playback parameters that set the playback speed, audio pitch scaling factor and
|
|
||||||
* whether to skip silence in the audio stream.
|
|
||||||
*
|
|
||||||
* @param speed The factor by which playback will be sped up. Must be greater than zero.
|
|
||||||
* @param pitch The factor by which the audio pitch will be scaled. Must be greater than zero.
|
|
||||||
* @param skipSilence Whether to skip silences in the audio stream.
|
* @param skipSilence Whether to skip silences in the audio stream.
|
||||||
*/
|
*/
|
||||||
public PlaybackParameters(float speed, float pitch, boolean skipSilence) {
|
public PlaybackParameters(float speed, boolean skipSilence) {
|
||||||
Assertions.checkArgument(speed > 0);
|
Assertions.checkArgument(speed > 0);
|
||||||
Assertions.checkArgument(pitch > 0);
|
|
||||||
this.speed = speed;
|
this.speed = speed;
|
||||||
this.pitch = pitch;
|
|
||||||
this.skipSilence = skipSilence;
|
this.skipSilence = skipSilence;
|
||||||
scaledUsPerMs = Math.round(speed * 1000f);
|
scaledUsPerMs = Math.round(speed * 1000f);
|
||||||
}
|
}
|
||||||
|
|
@ -97,7 +78,6 @@ public final class PlaybackParameters {
|
||||||
}
|
}
|
||||||
PlaybackParameters other = (PlaybackParameters) obj;
|
PlaybackParameters other = (PlaybackParameters) obj;
|
||||||
return this.speed == other.speed
|
return this.speed == other.speed
|
||||||
&& this.pitch == other.pitch
|
|
||||||
&& this.skipSilence == other.skipSilence;
|
&& this.skipSilence == other.skipSilence;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -105,7 +85,6 @@ public final class PlaybackParameters {
|
||||||
public int hashCode() {
|
public int hashCode() {
|
||||||
int result = 17;
|
int result = 17;
|
||||||
result = 31 * result + Float.floatToRawIntBits(speed);
|
result = 31 * result + Float.floatToRawIntBits(speed);
|
||||||
result = 31 * result + Float.floatToRawIntBits(pitch);
|
|
||||||
result = 31 * result + (skipSilence ? 1 : 0);
|
result = 31 * result + (skipSilence ? 1 : 0);
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -857,7 +857,7 @@ public class SimpleExoPlayer extends BasePlayer
|
||||||
PlaybackParameters playbackParameters;
|
PlaybackParameters playbackParameters;
|
||||||
if (params != null) {
|
if (params != null) {
|
||||||
params.allowDefaults();
|
params.allowDefaults();
|
||||||
playbackParameters = new PlaybackParameters(params.getSpeed(), params.getPitch());
|
playbackParameters = new PlaybackParameters(params.getSpeed());
|
||||||
} else {
|
} else {
|
||||||
playbackParameters = null;
|
playbackParameters = null;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -143,7 +143,6 @@ public final class DefaultAudioSink implements AudioSink {
|
||||||
silenceSkippingAudioProcessor.setEnabled(playbackParameters.skipSilence);
|
silenceSkippingAudioProcessor.setEnabled(playbackParameters.skipSilence);
|
||||||
return new PlaybackParameters(
|
return new PlaybackParameters(
|
||||||
sonicAudioProcessor.setSpeed(playbackParameters.speed),
|
sonicAudioProcessor.setSpeed(playbackParameters.speed),
|
||||||
sonicAudioProcessor.setPitch(playbackParameters.pitch),
|
|
||||||
playbackParameters.skipSilence);
|
playbackParameters.skipSilence);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -295,8 +294,8 @@ public final class DefaultAudioSink implements AudioSink {
|
||||||
* output. May be empty.
|
* output. May be empty.
|
||||||
* @param enableConvertHighResIntPcmToFloat Whether to enable conversion of high resolution
|
* @param enableConvertHighResIntPcmToFloat Whether to enable conversion of high resolution
|
||||||
* integer PCM to 32-bit float for output, if possible. Functionality that uses 16-bit integer
|
* integer PCM to 32-bit float for output, if possible. Functionality that uses 16-bit integer
|
||||||
* audio processing (for example, speed and pitch adjustment) will not be available when float
|
* audio processing (for example, speed adjustment) will not be available when float output is
|
||||||
* output is in use.
|
* in use.
|
||||||
*/
|
*/
|
||||||
public DefaultAudioSink(
|
public DefaultAudioSink(
|
||||||
@Nullable AudioCapabilities audioCapabilities,
|
@Nullable AudioCapabilities audioCapabilities,
|
||||||
|
|
@ -318,8 +317,8 @@ public final class DefaultAudioSink implements AudioSink {
|
||||||
* parameters adjustments. The instance passed in must not be reused in other sinks.
|
* parameters adjustments. The instance passed in must not be reused in other sinks.
|
||||||
* @param enableConvertHighResIntPcmToFloat Whether to enable conversion of high resolution
|
* @param enableConvertHighResIntPcmToFloat Whether to enable conversion of high resolution
|
||||||
* integer PCM to 32-bit float for output, if possible. Functionality that uses 16-bit integer
|
* integer PCM to 32-bit float for output, if possible. Functionality that uses 16-bit integer
|
||||||
* audio processing (for example, speed and pitch adjustment) will not be available when float
|
* audio processing (for example, speed adjustment) will not be available when float output is
|
||||||
* output is in use.
|
* in use.
|
||||||
*/
|
*/
|
||||||
public DefaultAudioSink(
|
public DefaultAudioSink(
|
||||||
@Nullable AudioCapabilities audioCapabilities,
|
@Nullable AudioCapabilities audioCapabilities,
|
||||||
|
|
|
||||||
|
|
@ -35,7 +35,6 @@ import java.util.Arrays;
|
||||||
private final int inputSampleRateHz;
|
private final int inputSampleRateHz;
|
||||||
private final int channelCount;
|
private final int channelCount;
|
||||||
private final float speed;
|
private final float speed;
|
||||||
private final float pitch;
|
|
||||||
private final float rate;
|
private final float rate;
|
||||||
private final int minPeriod;
|
private final int minPeriod;
|
||||||
private final int maxPeriod;
|
private final int maxPeriod;
|
||||||
|
|
@ -62,15 +61,12 @@ import java.util.Arrays;
|
||||||
* @param inputSampleRateHz The sample rate of input audio, in hertz.
|
* @param inputSampleRateHz The sample rate of input audio, in hertz.
|
||||||
* @param channelCount The number of channels in the input audio.
|
* @param channelCount The number of channels in the input audio.
|
||||||
* @param speed The speedup factor for output audio.
|
* @param speed The speedup factor for output audio.
|
||||||
* @param pitch The pitch factor for output audio.
|
|
||||||
* @param outputSampleRateHz The sample rate for output audio, in hertz.
|
* @param outputSampleRateHz The sample rate for output audio, in hertz.
|
||||||
*/
|
*/
|
||||||
public Sonic(
|
public Sonic(int inputSampleRateHz, int channelCount, float speed, int outputSampleRateHz) {
|
||||||
int inputSampleRateHz, int channelCount, float speed, float pitch, int outputSampleRateHz) {
|
|
||||||
this.inputSampleRateHz = inputSampleRateHz;
|
this.inputSampleRateHz = inputSampleRateHz;
|
||||||
this.channelCount = channelCount;
|
this.channelCount = channelCount;
|
||||||
this.speed = speed;
|
this.speed = speed;
|
||||||
this.pitch = pitch;
|
|
||||||
rate = (float) inputSampleRateHz / outputSampleRateHz;
|
rate = (float) inputSampleRateHz / outputSampleRateHz;
|
||||||
minPeriod = inputSampleRateHz / MAXIMUM_PITCH;
|
minPeriod = inputSampleRateHz / MAXIMUM_PITCH;
|
||||||
maxPeriod = inputSampleRateHz / MINIMUM_PITCH;
|
maxPeriod = inputSampleRateHz / MINIMUM_PITCH;
|
||||||
|
|
@ -120,10 +116,8 @@ import java.util.Arrays;
|
||||||
*/
|
*/
|
||||||
public void queueEndOfStream() {
|
public void queueEndOfStream() {
|
||||||
int remainingFrameCount = inputFrameCount;
|
int remainingFrameCount = inputFrameCount;
|
||||||
float s = speed / pitch;
|
|
||||||
float r = rate * pitch;
|
|
||||||
int expectedOutputFrames =
|
int expectedOutputFrames =
|
||||||
outputFrameCount + (int) ((remainingFrameCount / s + pitchFrameCount) / r + 0.5f);
|
outputFrameCount + (int) ((remainingFrameCount / speed + pitchFrameCount) / rate + 0.5f);
|
||||||
|
|
||||||
// Add enough silence to flush both input and pitch buffers.
|
// Add enough silence to flush both input and pitch buffers.
|
||||||
inputBuffer =
|
inputBuffer =
|
||||||
|
|
@ -468,16 +462,14 @@ import java.util.Arrays;
|
||||||
private void processStreamInput() {
|
private void processStreamInput() {
|
||||||
// Resample as many pitch periods as we have buffered on the input.
|
// Resample as many pitch periods as we have buffered on the input.
|
||||||
int originalOutputFrameCount = outputFrameCount;
|
int originalOutputFrameCount = outputFrameCount;
|
||||||
float s = speed / pitch;
|
if (speed > 1.00001 || speed < 0.99999) {
|
||||||
float r = rate * pitch;
|
changeSpeed(speed);
|
||||||
if (s > 1.00001 || s < 0.99999) {
|
|
||||||
changeSpeed(s);
|
|
||||||
} else {
|
} else {
|
||||||
copyToOutput(inputBuffer, 0, inputFrameCount);
|
copyToOutput(inputBuffer, 0, inputFrameCount);
|
||||||
inputFrameCount = 0;
|
inputFrameCount = 0;
|
||||||
}
|
}
|
||||||
if (r != 1.0f) {
|
if (rate != 1.0f) {
|
||||||
adjustRate(r, originalOutputFrameCount);
|
adjustRate(rate, originalOutputFrameCount);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -37,14 +37,6 @@ public final class SonicAudioProcessor implements AudioProcessor {
|
||||||
* The minimum allowed playback speed in {@link #setSpeed(float)}.
|
* The minimum allowed playback speed in {@link #setSpeed(float)}.
|
||||||
*/
|
*/
|
||||||
public static final float MINIMUM_SPEED = 0.1f;
|
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;
|
|
||||||
/**
|
/**
|
||||||
* Indicates that the output sample rate should be the same as the input.
|
* Indicates that the output sample rate should be the same as the input.
|
||||||
*/
|
*/
|
||||||
|
|
@ -63,7 +55,6 @@ public final class SonicAudioProcessor implements AudioProcessor {
|
||||||
|
|
||||||
private int pendingOutputSampleRate;
|
private int pendingOutputSampleRate;
|
||||||
private float speed;
|
private float speed;
|
||||||
private float pitch;
|
|
||||||
|
|
||||||
private AudioFormat pendingInputAudioFormat;
|
private AudioFormat pendingInputAudioFormat;
|
||||||
private AudioFormat pendingOutputAudioFormat;
|
private AudioFormat pendingOutputAudioFormat;
|
||||||
|
|
@ -84,7 +75,6 @@ public final class SonicAudioProcessor implements AudioProcessor {
|
||||||
*/
|
*/
|
||||||
public SonicAudioProcessor() {
|
public SonicAudioProcessor() {
|
||||||
speed = 1f;
|
speed = 1f;
|
||||||
pitch = 1f;
|
|
||||||
pendingInputAudioFormat = AudioFormat.NOT_SET;
|
pendingInputAudioFormat = AudioFormat.NOT_SET;
|
||||||
pendingOutputAudioFormat = AudioFormat.NOT_SET;
|
pendingOutputAudioFormat = AudioFormat.NOT_SET;
|
||||||
inputAudioFormat = AudioFormat.NOT_SET;
|
inputAudioFormat = AudioFormat.NOT_SET;
|
||||||
|
|
@ -112,23 +102,6 @@ public final class SonicAudioProcessor implements AudioProcessor {
|
||||||
return speed;
|
return speed;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets the playback pitch. This method may only be called after draining data through the
|
|
||||||
* processor. The value returned by {@link #isActive()} may change, and the processor must be
|
|
||||||
* {@link #flush() flushed} before queueing more data.
|
|
||||||
*
|
|
||||||
* @param pitch The requested new pitch.
|
|
||||||
* @return The actual new pitch.
|
|
||||||
*/
|
|
||||||
public float setPitch(float pitch) {
|
|
||||||
pitch = Util.constrainValue(pitch, MINIMUM_PITCH, MAXIMUM_PITCH);
|
|
||||||
if (this.pitch != pitch) {
|
|
||||||
this.pitch = pitch;
|
|
||||||
pendingSonicRecreation = true;
|
|
||||||
}
|
|
||||||
return pitch;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets the sample rate for output audio, in Hertz. Pass {@link #SAMPLE_RATE_NO_CHANGE} to output
|
* Sets the sample rate for output audio, in Hertz. Pass {@link #SAMPLE_RATE_NO_CHANGE} to output
|
||||||
* audio at the same sample rate as the input. After calling this method, call {@link
|
* audio at the same sample rate as the input. After calling this method, call {@link
|
||||||
|
|
@ -182,7 +155,6 @@ public final class SonicAudioProcessor implements AudioProcessor {
|
||||||
public boolean isActive() {
|
public boolean isActive() {
|
||||||
return pendingOutputAudioFormat.sampleRate != Format.NO_VALUE
|
return pendingOutputAudioFormat.sampleRate != Format.NO_VALUE
|
||||||
&& (Math.abs(speed - 1f) >= CLOSE_THRESHOLD
|
&& (Math.abs(speed - 1f) >= CLOSE_THRESHOLD
|
||||||
|| Math.abs(pitch - 1f) >= CLOSE_THRESHOLD
|
|
||||||
|| pendingOutputAudioFormat.sampleRate != pendingInputAudioFormat.sampleRate);
|
|| pendingOutputAudioFormat.sampleRate != pendingInputAudioFormat.sampleRate);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -243,7 +215,6 @@ public final class SonicAudioProcessor implements AudioProcessor {
|
||||||
inputAudioFormat.sampleRate,
|
inputAudioFormat.sampleRate,
|
||||||
inputAudioFormat.channelCount,
|
inputAudioFormat.channelCount,
|
||||||
speed,
|
speed,
|
||||||
pitch,
|
|
||||||
outputAudioFormat.sampleRate);
|
outputAudioFormat.sampleRate);
|
||||||
} else if (sonic != null) {
|
} else if (sonic != null) {
|
||||||
sonic.flush();
|
sonic.flush();
|
||||||
|
|
@ -258,7 +229,6 @@ public final class SonicAudioProcessor implements AudioProcessor {
|
||||||
@Override
|
@Override
|
||||||
public void reset() {
|
public void reset() {
|
||||||
speed = 1f;
|
speed = 1f;
|
||||||
pitch = 1f;
|
|
||||||
pendingInputAudioFormat = AudioFormat.NOT_SET;
|
pendingInputAudioFormat = AudioFormat.NOT_SET;
|
||||||
pendingOutputAudioFormat = AudioFormat.NOT_SET;
|
pendingOutputAudioFormat = AudioFormat.NOT_SET;
|
||||||
inputAudioFormat = AudioFormat.NOT_SET;
|
inputAudioFormat = AudioFormat.NOT_SET;
|
||||||
|
|
|
||||||
|
|
@ -150,8 +150,8 @@ public class EventLogger implements AnalyticsListener {
|
||||||
eventTime,
|
eventTime,
|
||||||
"playbackParameters",
|
"playbackParameters",
|
||||||
Util.formatInvariant(
|
Util.formatInvariant(
|
||||||
"speed=%.2f, pitch=%.2f, skipSilence=%s",
|
"speed=%.2f, skipSilence=%s",
|
||||||
playbackParameters.speed, playbackParameters.pitch, playbackParameters.skipSilence));
|
playbackParameters.speed, playbackParameters.skipSilence));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
||||||
|
|
@ -960,7 +960,7 @@ public final class ExoPlayerTest {
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
// Set playback parameters (while the fake media period is not yet prepared).
|
// Set playback parameters (while the fake media period is not yet prepared).
|
||||||
.setPlaybackParameters(new PlaybackParameters(/* speed= */ 2f, /* pitch= */ 2f))
|
.setPlaybackParameters(new PlaybackParameters(/* speed= */ 2f))
|
||||||
// Complete preparation of the fake media period.
|
// Complete preparation of the fake media period.
|
||||||
.executeRunnable(() -> fakeMediaPeriodHolder[0].setPreparationComplete())
|
.executeRunnable(() -> fakeMediaPeriodHolder[0].setPreparationComplete())
|
||||||
.build();
|
.build();
|
||||||
|
|
|
||||||
|
|
@ -85,14 +85,6 @@ public final class SonicAudioProcessorTest {
|
||||||
assertThat(sonicAudioProcessor.isActive()).isTrue();
|
assertThat(sonicAudioProcessor.isActive()).isTrue();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testIsActiveWithPitchChange() throws Exception {
|
|
||||||
sonicAudioProcessor.setPitch(1.5f);
|
|
||||||
sonicAudioProcessor.configure(AUDIO_FORMAT_44100_HZ);
|
|
||||||
sonicAudioProcessor.flush();
|
|
||||||
assertThat(sonicAudioProcessor.isActive()).isTrue();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testIsNotActiveWithNoChange() throws Exception {
|
public void testIsNotActiveWithNoChange() throws Exception {
|
||||||
sonicAudioProcessor.configure(AUDIO_FORMAT_44100_HZ);
|
sonicAudioProcessor.configure(AUDIO_FORMAT_44100_HZ);
|
||||||
|
|
|
||||||
|
|
@ -624,11 +624,7 @@ public abstract class Action {
|
||||||
"SetPlaybackParameters:"
|
"SetPlaybackParameters:"
|
||||||
+ (playbackParameters == null
|
+ (playbackParameters == null
|
||||||
? "null"
|
? "null"
|
||||||
: +playbackParameters.speed
|
: playbackParameters.speed + ":" + playbackParameters.skipSilence));
|
||||||
+ ":"
|
|
||||||
+ playbackParameters.pitch
|
|
||||||
+ ":"
|
|
||||||
+ playbackParameters.skipSilence));
|
|
||||||
this.playbackParameters = playbackParameters;
|
this.playbackParameters = playbackParameters;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue