Fix Playback speed and pitch for PlaybackManager
This commit is contained in:
parent
1033711357
commit
0fd8ab0ade
6 changed files with 54 additions and 68 deletions
|
@ -3,6 +3,7 @@ package code.name.monkey.retromusic.service
|
||||||
import code.name.monkey.retromusic.cast.CastHelper.toMediaInfo
|
import code.name.monkey.retromusic.cast.CastHelper.toMediaInfo
|
||||||
import code.name.monkey.retromusic.model.Song
|
import code.name.monkey.retromusic.model.Song
|
||||||
import code.name.monkey.retromusic.service.playback.Playback
|
import code.name.monkey.retromusic.service.playback.Playback
|
||||||
|
import code.name.monkey.retromusic.util.PreferenceUtil.playbackSpeed
|
||||||
import com.google.android.gms.cast.MediaLoadOptions
|
import com.google.android.gms.cast.MediaLoadOptions
|
||||||
import com.google.android.gms.cast.MediaSeekOptions
|
import com.google.android.gms.cast.MediaSeekOptions
|
||||||
import com.google.android.gms.cast.MediaStatus
|
import com.google.android.gms.cast.MediaStatus
|
||||||
|
@ -18,6 +19,7 @@ class CastPlayer(castSession: CastSession) : Playback,
|
||||||
|
|
||||||
init {
|
init {
|
||||||
remoteMediaClient?.registerCallback(this)
|
remoteMediaClient?.registerCallback(this)
|
||||||
|
remoteMediaClient?.setPlaybackRate(playbackSpeed.toDouble().coerceIn(0.5, 2.0))
|
||||||
}
|
}
|
||||||
|
|
||||||
private var isActuallyPlaying = false
|
private var isActuallyPlaying = false
|
||||||
|
@ -93,6 +95,10 @@ class CastPlayer(castSession: CastSession) : Playback,
|
||||||
|
|
||||||
override fun setCrossFadeDuration(duration: Int) {}
|
override fun setCrossFadeDuration(duration: Int) {}
|
||||||
|
|
||||||
|
override fun setPlaybackSpeedPitch(speed: Float, pitch: Float) {
|
||||||
|
remoteMediaClient?.setPlaybackRate(speed.toDouble().coerceIn(0.5, 2.0))
|
||||||
|
}
|
||||||
|
|
||||||
override fun onStatusUpdated() {
|
override fun onStatusUpdated() {
|
||||||
when (remoteMediaClient?.playerState) {
|
when (remoteMediaClient?.playerState) {
|
||||||
MediaStatus.PLAYER_STATE_IDLE -> {
|
MediaStatus.PLAYER_STATE_IDLE -> {
|
||||||
|
|
|
@ -2,12 +2,10 @@ package code.name.monkey.retromusic.service
|
||||||
|
|
||||||
import android.animation.Animator
|
import android.animation.Animator
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
|
||||||
import android.media.AudioAttributes
|
import android.media.AudioAttributes
|
||||||
import android.media.AudioManager
|
import android.media.AudioManager
|
||||||
import android.media.MediaPlayer
|
import android.media.MediaPlayer
|
||||||
import android.media.PlaybackParams
|
import android.media.PlaybackParams
|
||||||
import android.media.audiofx.AudioEffect
|
|
||||||
import android.os.PowerManager
|
import android.os.PowerManager
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import androidx.core.net.toUri
|
import androidx.core.net.toUri
|
||||||
|
@ -20,7 +18,6 @@ import code.name.monkey.retromusic.model.Song
|
||||||
import code.name.monkey.retromusic.service.AudioFader.Companion.createFadeAnimator
|
import code.name.monkey.retromusic.service.AudioFader.Companion.createFadeAnimator
|
||||||
import code.name.monkey.retromusic.service.playback.Playback
|
import code.name.monkey.retromusic.service.playback.Playback
|
||||||
import code.name.monkey.retromusic.service.playback.Playback.PlaybackCallbacks
|
import code.name.monkey.retromusic.service.playback.Playback.PlaybackCallbacks
|
||||||
import code.name.monkey.retromusic.util.MusicUtil
|
|
||||||
import code.name.monkey.retromusic.util.PreferenceUtil
|
import code.name.monkey.retromusic.util.PreferenceUtil
|
||||||
import code.name.monkey.retromusic.util.PreferenceUtil.playbackPitch
|
import code.name.monkey.retromusic.util.PreferenceUtil.playbackPitch
|
||||||
import code.name.monkey.retromusic.util.PreferenceUtil.playbackSpeed
|
import code.name.monkey.retromusic.util.PreferenceUtil.playbackSpeed
|
||||||
|
@ -144,6 +141,7 @@ class CrossFadePlayer(val context: Context) : Playback, MediaPlayer.OnCompletion
|
||||||
}
|
}
|
||||||
hasDataSource = true
|
hasDataSource = true
|
||||||
} else {
|
} else {
|
||||||
|
completion(true)
|
||||||
mIsInitialized = true
|
mIsInitialized = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -170,7 +168,7 @@ class CrossFadePlayer(val context: Context) : Playback, MediaPlayer.OnCompletion
|
||||||
player.setAudioAttributes(
|
player.setAudioAttributes(
|
||||||
AudioAttributes.Builder().setLegacyStreamType(AudioManager.STREAM_MUSIC).build()
|
AudioAttributes.Builder().setLegacyStreamType(AudioManager.STREAM_MUSIC).build()
|
||||||
)
|
)
|
||||||
player.setPlaybackSpeedPitch(PreferenceUtil.playbackSpeed, PreferenceUtil.playbackPitch)
|
player.setPlaybackSpeedPitch(playbackSpeed, playbackPitch)
|
||||||
player.setOnPreparedListener {
|
player.setOnPreparedListener {
|
||||||
player.setOnPreparedListener(null)
|
player.setOnPreparedListener(null)
|
||||||
completion(true)
|
completion(true)
|
||||||
|
@ -329,8 +327,7 @@ class CrossFadePlayer(val context: Context) : Playback, MediaPlayer.OnCompletion
|
||||||
getNextPlayer()?.let { player ->
|
getNextPlayer()?.let { player ->
|
||||||
val nextSong = MusicPlayerRemote.nextSong
|
val nextSong = MusicPlayerRemote.nextSong
|
||||||
if (nextSong != null) {
|
if (nextSong != null) {
|
||||||
setDataSourceImpl(player,
|
setDataSourceImpl(player, nextSong.uri.toString()) { success ->
|
||||||
MusicUtil.getSongFileUri(nextSong.id).toString()) { success ->
|
|
||||||
// Switch to other player (Crossfade) only if next song exists
|
// Switch to other player (Crossfade) only if next song exists
|
||||||
if (success) switchPlayer()
|
if (success) switchPlayer()
|
||||||
}
|
}
|
||||||
|
@ -358,16 +355,7 @@ class CrossFadePlayer(val context: Context) : Playback, MediaPlayer.OnCompletion
|
||||||
|
|
||||||
override fun setPlaybackSpeedPitch(speed: Float, pitch: Float) {
|
override fun setPlaybackSpeedPitch(speed: Float, pitch: Float) {
|
||||||
getCurrentPlayer()?.setPlaybackSpeedPitch(speed, pitch)
|
getCurrentPlayer()?.setPlaybackSpeedPitch(speed, pitch)
|
||||||
}
|
getNextPlayer()?.setPlaybackSpeedPitch(speed, pitch)
|
||||||
|
|
||||||
private fun MediaPlayer.setPlaybackSpeedPitch(speed: Float, pitch: Float) {
|
|
||||||
if (hasMarshmallow()) {
|
|
||||||
val wasPlaying: Boolean = isPlaying
|
|
||||||
playbackParams = PlaybackParams().setSpeed(speed).setPitch(pitch)
|
|
||||||
if (!wasPlaying) {
|
|
||||||
if (isPlaying) pause()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
@ -375,4 +363,14 @@ class CrossFadePlayer(val context: Context) : Playback, MediaPlayer.OnCompletion
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
internal fun crossFadeScope(): CoroutineScope = CoroutineScope(Job() + Dispatchers.Main)
|
internal fun crossFadeScope(): CoroutineScope = CoroutineScope(Job() + Dispatchers.Main)
|
||||||
|
|
||||||
|
fun MediaPlayer.setPlaybackSpeedPitch(speed: Float, pitch: Float) {
|
||||||
|
if (hasMarshmallow()) {
|
||||||
|
val wasPlaying: Boolean = isPlaying
|
||||||
|
playbackParams = PlaybackParams().setSpeed(speed).setPitch(pitch)
|
||||||
|
if (!wasPlaying) {
|
||||||
|
if (isPlaying) pause()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -14,19 +14,12 @@
|
||||||
package code.name.monkey.retromusic.service
|
package code.name.monkey.retromusic.service
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.SharedPreferences
|
|
||||||
import android.content.SharedPreferences.OnSharedPreferenceChangeListener
|
|
||||||
import android.media.AudioAttributes
|
import android.media.AudioAttributes
|
||||||
import android.media.MediaPlayer
|
import android.media.MediaPlayer
|
||||||
import android.media.MediaPlayer.OnCompletionListener
|
import android.media.MediaPlayer.OnCompletionListener
|
||||||
import android.media.PlaybackParams
|
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import android.os.PowerManager
|
import android.os.PowerManager
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import androidx.preference.PreferenceManager
|
|
||||||
import code.name.monkey.appthemehelper.util.VersionUtils.hasMarshmallow
|
|
||||||
import code.name.monkey.retromusic.PLAYBACK_PITCH
|
|
||||||
import code.name.monkey.retromusic.PLAYBACK_SPEED
|
|
||||||
import code.name.monkey.retromusic.R
|
import code.name.monkey.retromusic.R
|
||||||
import code.name.monkey.retromusic.extensions.showToast
|
import code.name.monkey.retromusic.extensions.showToast
|
||||||
import code.name.monkey.retromusic.extensions.uri
|
import code.name.monkey.retromusic.extensions.uri
|
||||||
|
@ -41,7 +34,7 @@ import code.name.monkey.retromusic.util.PreferenceUtil.playbackSpeed
|
||||||
* @author Andrew Neal, Karim Abou Zeid (kabouzeid)
|
* @author Andrew Neal, Karim Abou Zeid (kabouzeid)
|
||||||
*/
|
*/
|
||||||
class MultiPlayer internal constructor(private val context: Context) : Playback,
|
class MultiPlayer internal constructor(private val context: Context) : Playback,
|
||||||
MediaPlayer.OnErrorListener, OnCompletionListener, OnSharedPreferenceChangeListener {
|
MediaPlayer.OnErrorListener, OnCompletionListener {
|
||||||
private var mCurrentMediaPlayer = MediaPlayer()
|
private var mCurrentMediaPlayer = MediaPlayer()
|
||||||
private var mNextMediaPlayer: MediaPlayer? = null
|
private var mNextMediaPlayer: MediaPlayer? = null
|
||||||
private var callbacks: PlaybackCallbacks? = null
|
private var callbacks: PlaybackCallbacks? = null
|
||||||
|
@ -52,6 +45,10 @@ class MultiPlayer internal constructor(private val context: Context) : Playback,
|
||||||
override var isInitialized = false
|
override var isInitialized = false
|
||||||
private set
|
private set
|
||||||
|
|
||||||
|
init {
|
||||||
|
mCurrentMediaPlayer.setWakeMode(context, PowerManager.PARTIAL_WAKE_LOCK)
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param song The song object you want to play
|
* @param song The song object you want to play
|
||||||
* @return True if the `player` has been prepared and is ready to play, false otherwise
|
* @return True if the `player` has been prepared and is ready to play, false otherwise
|
||||||
|
@ -89,12 +86,12 @@ class MultiPlayer internal constructor(private val context: Context) : Playback,
|
||||||
} else {
|
} else {
|
||||||
player.setDataSource(path)
|
player.setDataSource(path)
|
||||||
}
|
}
|
||||||
setPlaybackSpeedPitch(player)
|
|
||||||
player.setAudioAttributes(AudioAttributes.Builder()
|
player.setAudioAttributes(AudioAttributes.Builder()
|
||||||
.setUsage(AudioAttributes.USAGE_MEDIA)
|
.setUsage(AudioAttributes.USAGE_MEDIA)
|
||||||
.setContentType(AudioAttributes.CONTENT_TYPE_MUSIC)
|
.setContentType(AudioAttributes.CONTENT_TYPE_MUSIC)
|
||||||
.build()
|
.build()
|
||||||
)
|
)
|
||||||
|
player.setPlaybackSpeedPitch(playbackSpeed, playbackPitch)
|
||||||
player.setOnPreparedListener {
|
player.setOnPreparedListener {
|
||||||
player.setOnPreparedListener(null)
|
player.setOnPreparedListener(null)
|
||||||
completion(true)
|
completion(true)
|
||||||
|
@ -198,8 +195,6 @@ class MultiPlayer internal constructor(private val context: Context) : Playback,
|
||||||
if (mNextMediaPlayer != null) {
|
if (mNextMediaPlayer != null) {
|
||||||
mNextMediaPlayer?.release()
|
mNextMediaPlayer?.release()
|
||||||
}
|
}
|
||||||
PreferenceManager.getDefaultSharedPreferences(context)
|
|
||||||
.unregisterOnSharedPreferenceChangeListener(this)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -298,9 +293,6 @@ class MultiPlayer internal constructor(private val context: Context) : Playback,
|
||||||
override val audioSessionId: Int
|
override val audioSessionId: Int
|
||||||
get() = mCurrentMediaPlayer.audioSessionId
|
get() = mCurrentMediaPlayer.audioSessionId
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritDoc}
|
|
||||||
*/
|
|
||||||
override fun onError(mp: MediaPlayer, what: Int, extra: Int): Boolean {
|
override fun onError(mp: MediaPlayer, what: Int, extra: Int): Boolean {
|
||||||
isInitialized = false
|
isInitialized = false
|
||||||
mCurrentMediaPlayer.release()
|
mCurrentMediaPlayer.release()
|
||||||
|
@ -311,9 +303,6 @@ class MultiPlayer internal constructor(private val context: Context) : Playback,
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritDoc}
|
|
||||||
*/
|
|
||||||
override fun onCompletion(mp: MediaPlayer) {
|
override fun onCompletion(mp: MediaPlayer) {
|
||||||
if (mp == mCurrentMediaPlayer && mNextMediaPlayer != null) {
|
if (mp == mCurrentMediaPlayer && mNextMediaPlayer != null) {
|
||||||
isInitialized = false
|
isInitialized = false
|
||||||
|
@ -328,34 +317,12 @@ class MultiPlayer internal constructor(private val context: Context) : Playback,
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun setCrossFadeDuration(duration: Int) {}
|
override fun setCrossFadeDuration(duration: Int) {}
|
||||||
override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences, key: String) {
|
|
||||||
if (key == PLAYBACK_SPEED || key == PLAYBACK_PITCH) {
|
|
||||||
setPlaybackSpeedPitch(mCurrentMediaPlayer)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun setPlaybackSpeedPitch(mp: MediaPlayer) {
|
override fun setPlaybackSpeedPitch(speed: Float, pitch: Float) {
|
||||||
if (hasMarshmallow()) {
|
mCurrentMediaPlayer.setPlaybackSpeedPitch(speed, pitch)
|
||||||
val wasPlaying = mp.isPlaying
|
|
||||||
mp.playbackParams = PlaybackParams()
|
|
||||||
.setSpeed(playbackSpeed)
|
|
||||||
.setPitch(playbackPitch)
|
|
||||||
if (!wasPlaying) {
|
|
||||||
if (mp.isPlaying) mp.pause()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
val TAG: String = MultiPlayer::class.java.simpleName
|
val TAG: String = MultiPlayer::class.java.simpleName
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Constructor of `MultiPlayer`
|
|
||||||
*/
|
|
||||||
init {
|
|
||||||
mCurrentMediaPlayer.setWakeMode(context, PowerManager.PARTIAL_WAKE_LOCK)
|
|
||||||
PreferenceManager.getDefaultSharedPreferences(context)
|
|
||||||
.registerOnSharedPreferenceChangeListener(this)
|
|
||||||
}
|
|
||||||
}
|
}
|
|
@ -692,15 +692,15 @@ class MusicService : MediaBrowserServiceCompat(),
|
||||||
) {
|
) {
|
||||||
when (key) {
|
when (key) {
|
||||||
PLAYBACK_SPEED, PLAYBACK_PITCH -> {
|
PLAYBACK_SPEED, PLAYBACK_PITCH -> {
|
||||||
playback?.setPlaybackSpeedPitch(playbackSpeed, playbackPitch)
|
playbackManager.setPlaybackSpeedPitch(playbackSpeed, playbackPitch)
|
||||||
}
|
}
|
||||||
CROSS_FADE_DURATION -> {
|
CROSS_FADE_DURATION -> {
|
||||||
val progress = songProgressMillis
|
val progress = songProgressMillis
|
||||||
val wasPlaying = isPlaying
|
val wasPlaying = isPlaying
|
||||||
|
|
||||||
playbackManager.maybeSwitchToCrossFade(crossFadeDuration)
|
if (playbackManager.maybeSwitchToCrossFade(crossFadeDuration)) {
|
||||||
restorePlaybackState(wasPlaying, progress)
|
restorePlaybackState(wasPlaying, progress)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
ALBUM_ART_ON_LOCK_SCREEN, BLURRED_ALBUM_ART -> updateMediaSessionMetaData()
|
ALBUM_ART_ON_LOCK_SCREEN, BLURRED_ALBUM_ART -> updateMediaSessionMetaData()
|
||||||
COLORED_NOTIFICATION -> {
|
COLORED_NOTIFICATION -> {
|
||||||
|
|
|
@ -97,7 +97,6 @@ internal class PlaybackHandler(service: MusicService, looper: Looper) : Handler(
|
||||||
}
|
}
|
||||||
AudioManager.AUDIOFOCUS_LOSS -> {
|
AudioManager.AUDIOFOCUS_LOSS -> {
|
||||||
// Lost focus for an unbounded amount of time: stop playback and release media playback
|
// Lost focus for an unbounded amount of time: stop playback and release media playback
|
||||||
val isAudioFocusEnabled = isAudioFocusEnabled
|
|
||||||
if (!isAudioFocusEnabled) {
|
if (!isAudioFocusEnabled) {
|
||||||
service.pause(true)
|
service.pause(true)
|
||||||
}
|
}
|
||||||
|
|
|
@ -92,8 +92,12 @@ class PlaybackManager(val context: Context) {
|
||||||
playback?.setCrossFadeDuration(duration)
|
playback?.setCrossFadeDuration(duration)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun maybeSwitchToCrossFade(crossFadeDuration: Int) {
|
/**
|
||||||
/* Switch to MultiPlayer if Crossfade duration is 0 and
|
* @param crossFadeDuration CrossFade duration
|
||||||
|
* @return Whether switched playback
|
||||||
|
*/
|
||||||
|
fun maybeSwitchToCrossFade(crossFadeDuration: Int): Boolean {
|
||||||
|
/* Switch to MultiPlayer if CrossFade duration is 0 and
|
||||||
Playback is not an instance of MultiPlayer */
|
Playback is not an instance of MultiPlayer */
|
||||||
if (playback !is MultiPlayer && crossFadeDuration == 0) {
|
if (playback !is MultiPlayer && crossFadeDuration == 0) {
|
||||||
if (playback != null) {
|
if (playback != null) {
|
||||||
|
@ -101,13 +105,16 @@ class PlaybackManager(val context: Context) {
|
||||||
}
|
}
|
||||||
playback = null
|
playback = null
|
||||||
playback = MultiPlayer(context)
|
playback = MultiPlayer(context)
|
||||||
|
return true
|
||||||
} else if (playback !is CrossFadePlayer && crossFadeDuration > 0) {
|
} else if (playback !is CrossFadePlayer && crossFadeDuration > 0) {
|
||||||
if (playback != null) {
|
if (playback != null) {
|
||||||
playback?.release()
|
playback?.release()
|
||||||
}
|
}
|
||||||
playback = null
|
playback = null
|
||||||
playback = CrossFadePlayer(context)
|
playback = CrossFadePlayer(context)
|
||||||
|
return true
|
||||||
}
|
}
|
||||||
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
fun release() {
|
fun release() {
|
||||||
|
@ -126,7 +133,7 @@ class PlaybackManager(val context: Context) {
|
||||||
context.sendBroadcast(intent)
|
context.sendBroadcast(intent)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun closeAudioEffectSession() {
|
private fun closeAudioEffectSession() {
|
||||||
val audioEffectsIntent = Intent(AudioEffect.ACTION_CLOSE_AUDIO_EFFECT_CONTROL_SESSION)
|
val audioEffectsIntent = Intent(AudioEffect.ACTION_CLOSE_AUDIO_EFFECT_CONTROL_SESSION)
|
||||||
if (playback != null) {
|
if (playback != null) {
|
||||||
audioEffectsIntent.putExtra(AudioEffect.EXTRA_AUDIO_SESSION,
|
audioEffectsIntent.putExtra(AudioEffect.EXTRA_AUDIO_SESSION,
|
||||||
|
@ -141,12 +148,18 @@ class PlaybackManager(val context: Context) {
|
||||||
switchToPlayback(createLocalPlayback(), onChange)
|
switchToPlayback(createLocalPlayback(), onChange)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun switchToRemotePlayback(castSession: CastSession, onChange: (wasPlaying: Boolean, progress: Int) -> Unit) {
|
fun switchToRemotePlayback(
|
||||||
|
castSession: CastSession,
|
||||||
|
onChange: (wasPlaying: Boolean, progress: Int) -> Unit,
|
||||||
|
) {
|
||||||
playbackLocation = PlaybackLocation.REMOTE
|
playbackLocation = PlaybackLocation.REMOTE
|
||||||
switchToPlayback(CastPlayer(castSession), onChange)
|
switchToPlayback(CastPlayer(castSession), onChange)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun switchToPlayback(playback: Playback, onChange: (wasPlaying: Boolean, progress: Int) -> Unit) {
|
private fun switchToPlayback(
|
||||||
|
playback: Playback,
|
||||||
|
onChange: (wasPlaying: Boolean, progress: Int) -> Unit,
|
||||||
|
) {
|
||||||
val oldPlayback = playback
|
val oldPlayback = playback
|
||||||
val wasPlaying: Boolean = oldPlayback.isPlaying
|
val wasPlaying: Boolean = oldPlayback.isPlaying
|
||||||
val progress: Int = oldPlayback.position()
|
val progress: Int = oldPlayback.position()
|
||||||
|
@ -167,6 +180,9 @@ class PlaybackManager(val context: Context) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun setPlaybackSpeedPitch(playbackSpeed: Float, playbackPitch: Float) {
|
||||||
|
playback?.setPlaybackSpeedPitch(playbackSpeed, playbackPitch)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
enum class PlaybackLocation {
|
enum class PlaybackLocation {
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue