Added ability to set Playback speed and pitch

This commit is contained in:
Prathamesh More 2022-01-05 14:26:36 +05:30
parent 3a6645ab35
commit 943de60f23
12 changed files with 229 additions and 18 deletions

View file

@ -155,3 +155,5 @@ const val WHITELIST_MUSIC = "whitelist_music"
const val MATERIAL_YOU = "material_you" const val MATERIAL_YOU = "material_you"
const val SNOWFALL = "snowfall" const val SNOWFALL = "snowfall"
const val LYRICS_TYPE = "lyrics_type" const val LYRICS_TYPE = "lyrics_type"
const val PLAYBACK_SPEED = "playback_speed"
const val PLAYBACK_PITCH = "playback_pitch"

View file

@ -0,0 +1,52 @@
package code.name.monkey.retromusic.dialogs
import android.app.Dialog
import android.os.Bundle
import androidx.fragment.app.DialogFragment
import code.name.monkey.retromusic.R
import code.name.monkey.retromusic.databinding.DialogPlaybackSpeedBinding
import code.name.monkey.retromusic.extensions.accent
import code.name.monkey.retromusic.extensions.colorButtons
import code.name.monkey.retromusic.extensions.materialDialog
import code.name.monkey.retromusic.util.PreferenceUtil
import com.google.android.material.slider.Slider
class PlaybackSpeedDialog : DialogFragment() {
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
val binding = DialogPlaybackSpeedBinding.inflate(layoutInflater)
binding.playbackSpeedSlider.accent()
binding.playbackPitchSlider.accent()
binding.playbackSpeedSlider.addOnChangeListener(Slider.OnChangeListener { _, value, _ ->
binding.speedValue.text = "$value"
})
binding.playbackPitchSlider.addOnChangeListener(Slider.OnChangeListener { _, value, _ ->
binding.pitchValue.text = "$value"
})
binding.playbackSpeedSlider.value = PreferenceUtil.playbackSpeed
binding.playbackPitchSlider.value = PreferenceUtil.playbackPitch
return materialDialog(R.string.playback_settings)
.setNegativeButton(android.R.string.cancel, null)
.setPositiveButton(R.string.save) { _, _ ->
updatePlaybackAndPitch(
binding.playbackSpeedSlider.value,
binding.playbackPitchSlider.value
)
}
.setView(binding.root)
.create()
.colorButtons()
}
private fun updatePlaybackAndPitch(speed: Float, pitch: Float) {
PreferenceUtil.playbackSpeed = speed
PreferenceUtil.playbackPitch = pitch
}
companion object {
fun newInstance(): PlaybackSpeedDialog {
return PlaybackSpeedDialog()
}
}
}

View file

@ -43,6 +43,7 @@ import com.google.android.material.button.MaterialButton
import com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton import com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton
import com.google.android.material.floatingactionbutton.FloatingActionButton import com.google.android.material.floatingactionbutton.FloatingActionButton
import com.google.android.material.progressindicator.CircularProgressIndicator import com.google.android.material.progressindicator.CircularProgressIndicator
import com.google.android.material.slider.Slider
import com.google.android.material.textfield.TextInputLayout import com.google.android.material.textfield.TextInputLayout
fun Int.ripAlpha(): Int { fun Int.ripAlpha(): Int {
@ -112,6 +113,12 @@ fun SeekBar.addAccentColor() {
thumbTintList = colorState thumbTintList = colorState
} }
fun Slider.accent() {
if (materialYou) return
trackActiveTintList = context.accentColor().colorStateList
trackInactiveTintList = context.accentColorVariant().colorStateList
}
fun Button.accentTextColor() { fun Button.accentTextColor() {
if (materialYou) return if (materialYou) return
setTextColor(ThemeStore.accentColor(App.getContext())) setTextColor(ThemeStore.accentColor(App.getContext()))
@ -269,6 +276,15 @@ fun Context.darkAccentColorVariant(): Int {
) )
} }
@ColorInt
fun Context.accentColorVariant(): Int {
return if (surfaceColor().isColorLight) {
accentColor().darkerColor
} else {
accentColor().lighterColor
}
}
inline val @receiver:ColorInt Int.isColorLight inline val @receiver:ColorInt Int.isColorLight
get() = ColorUtil.isColorLight(this) get() = ColorUtil.isColorLight(this)
@ -277,3 +293,6 @@ inline val @receiver:ColorInt Int.lighterColor
inline val @receiver:ColorInt Int.darkerColor inline val @receiver:ColorInt Int.darkerColor
get() = ColorUtil.darkenColor(this) get() = ColorUtil.darkenColor(this)
inline val Int.colorStateList : ColorStateList
get() = ColorStateList.valueOf(this)

View file

@ -42,6 +42,7 @@ import androidx.viewpager.widget.ViewPager
import code.name.monkey.appthemehelper.util.VersionUtils import code.name.monkey.appthemehelper.util.VersionUtils
import code.name.monkey.retromusic.EXTRA_ALBUM_ID import code.name.monkey.retromusic.EXTRA_ALBUM_ID
import code.name.monkey.retromusic.EXTRA_ARTIST_ID import code.name.monkey.retromusic.EXTRA_ARTIST_ID
import code.name.monkey.retromusic.PLAYBACK_SPEED
import code.name.monkey.retromusic.R import code.name.monkey.retromusic.R
import code.name.monkey.retromusic.activities.MainActivity import code.name.monkey.retromusic.activities.MainActivity
import code.name.monkey.retromusic.activities.tageditor.AbsTagEditorActivity import code.name.monkey.retromusic.activities.tageditor.AbsTagEditorActivity
@ -79,6 +80,10 @@ abstract class AbsPlayerFragment(@LayoutRes layout: Int) : AbsMainActivityFragme
): Boolean { ): Boolean {
val song = MusicPlayerRemote.currentSong val song = MusicPlayerRemote.currentSong
when (item.itemId) { when (item.itemId) {
R.id.action_playback_speed -> {
PlaybackSpeedDialog.newInstance().show(childFragmentManager, "PLAYBACK_SETTINGS")
return true
}
R.id.action_toggle_lyrics -> { R.id.action_toggle_lyrics -> {
PreferenceUtil.showLyrics = !item.isChecked PreferenceUtil.showLyrics = !item.isChecked
item.isChecked = !item.isChecked item.isChecked = !item.isChecked

View file

@ -1,17 +1,3 @@
/*
* Copyright (c) 2019 Hemanth Savarala.
*
* Licensed under the GNU General Public License v3
*
* This is free software: you can redistribute it and/or modify it under
* the terms of the GNU General Public License as published by
* the Free Software Foundation either version 3 of the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
*/
package code.name.monkey.retromusic.preferences package code.name.monkey.retromusic.preferences
import android.app.Dialog import android.app.Dialog

View file

@ -16,8 +16,10 @@ package code.name.monkey.retromusic.service;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.content.SharedPreferences;
import android.media.AudioManager; import android.media.AudioManager;
import android.media.MediaPlayer; import android.media.MediaPlayer;
import android.media.PlaybackParams;
import android.media.audiofx.AudioEffect; import android.media.audiofx.AudioEffect;
import android.net.Uri; import android.net.Uri;
import android.os.PowerManager; import android.os.PowerManager;
@ -26,7 +28,10 @@ import android.widget.Toast;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.preference.PreferenceManager;
import code.name.monkey.appthemehelper.util.VersionUtils;
import code.name.monkey.retromusic.ConstantsKt;
import code.name.monkey.retromusic.R; import code.name.monkey.retromusic.R;
import code.name.monkey.retromusic.service.playback.Playback; import code.name.monkey.retromusic.service.playback.Playback;
import code.name.monkey.retromusic.util.PreferenceUtil; import code.name.monkey.retromusic.util.PreferenceUtil;
@ -35,7 +40,7 @@ import code.name.monkey.retromusic.util.PreferenceUtil;
* @author Andrew Neal, Karim Abou Zeid (kabouzeid) * @author Andrew Neal, Karim Abou Zeid (kabouzeid)
*/ */
public class MultiPlayer public class MultiPlayer
implements Playback, MediaPlayer.OnErrorListener, MediaPlayer.OnCompletionListener { implements Playback, MediaPlayer.OnErrorListener, MediaPlayer.OnCompletionListener, SharedPreferences.OnSharedPreferenceChangeListener {
public static final String TAG = MultiPlayer.class.getSimpleName(); public static final String TAG = MultiPlayer.class.getSimpleName();
private MediaPlayer mCurrentMediaPlayer = new MediaPlayer(); private MediaPlayer mCurrentMediaPlayer = new MediaPlayer();
@ -53,6 +58,7 @@ public class MultiPlayer
MultiPlayer(final Context context) { MultiPlayer(final Context context) {
this.context = context; this.context = context;
mCurrentMediaPlayer.setWakeMode(context, PowerManager.PARTIAL_WAKE_LOCK); mCurrentMediaPlayer.setWakeMode(context, PowerManager.PARTIAL_WAKE_LOCK);
PreferenceManager.getDefaultSharedPreferences(context).registerOnSharedPreferenceChangeListener(this);
} }
/** /**
@ -86,6 +92,7 @@ public class MultiPlayer
} else { } else {
player.setDataSource(path); player.setDataSource(path);
} }
setPlaybackSpeedPitch(player);
player.setAudioStreamType(AudioManager.STREAM_MUSIC); player.setAudioStreamType(AudioManager.STREAM_MUSIC);
player.prepare(); player.prepare();
} catch (Exception e) { } catch (Exception e) {
@ -199,6 +206,7 @@ public class MultiPlayer
if (mNextMediaPlayer != null) { if (mNextMediaPlayer != null) {
mNextMediaPlayer.release(); mNextMediaPlayer.release();
} }
PreferenceManager.getDefaultSharedPreferences(context).unregisterOnSharedPreferenceChangeListener(this);
} }
/** /**
@ -346,4 +354,23 @@ public class MultiPlayer
@Override @Override
public void setCrossFadeDuration(int duration) { public void setCrossFadeDuration(int duration) {
} }
@Override
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
if (key.equals(ConstantsKt.PLAYBACK_SPEED) || key.equals(ConstantsKt.PLAYBACK_PITCH)) {
setPlaybackSpeedPitch(mCurrentMediaPlayer);
}
}
public void setPlaybackSpeedPitch(MediaPlayer mp) {
if (VersionUtils.INSTANCE.hasMarshmallow()) {
boolean wasPlaying = mp.isPlaying();
mp.setPlaybackParams(new PlaybackParams()
.setSpeed(PreferenceUtil.INSTANCE.getPlaybackSpeed())
.setPitch(PreferenceUtil.INSTANCE.getPlaybackPitch()));
if (!wasPlaying) {
if (mp.isPlaying()) mp.pause();
}
}
}
} }

View file

@ -22,6 +22,7 @@ import static code.name.monkey.retromusic.ConstantsKt.BLURRED_ALBUM_ART;
import static code.name.monkey.retromusic.ConstantsKt.CLASSIC_NOTIFICATION; import static code.name.monkey.retromusic.ConstantsKt.CLASSIC_NOTIFICATION;
import static code.name.monkey.retromusic.ConstantsKt.COLORED_NOTIFICATION; import static code.name.monkey.retromusic.ConstantsKt.COLORED_NOTIFICATION;
import static code.name.monkey.retromusic.ConstantsKt.CROSS_FADE_DURATION; import static code.name.monkey.retromusic.ConstantsKt.CROSS_FADE_DURATION;
import static code.name.monkey.retromusic.ConstantsKt.PLAYBACK_SPEED;
import static code.name.monkey.retromusic.ConstantsKt.TOGGLE_HEADSET; import static code.name.monkey.retromusic.ConstantsKt.TOGGLE_HEADSET;
import static code.name.monkey.retromusic.service.AudioFader.startFadeAnimator; import static code.name.monkey.retromusic.service.AudioFader.startFadeAnimator;
@ -940,6 +941,9 @@ public class MusicService extends MediaBrowserServiceCompat
playingNotification.setPlaying(isPlaying()); playingNotification.setPlaying(isPlaying());
playingNotification.updateMetadata(getCurrentSong(), this::startForegroundOrNotify); playingNotification.updateMetadata(getCurrentSong(), this::startForegroundOrNotify);
break; break;
case PLAYBACK_SPEED:
updateMediaSessionPlaybackState();
break;
case TOGGLE_HEADSET: case TOGGLE_HEADSET:
registerHeadsetEvents(); registerHeadsetEvents();
break; break;
@ -1334,7 +1338,7 @@ public class MusicService extends MediaBrowserServiceCompat
.setState( .setState(
isPlaying() ? PlaybackStateCompat.STATE_PLAYING : PlaybackStateCompat.STATE_PAUSED, isPlaying() ? PlaybackStateCompat.STATE_PLAYING : PlaybackStateCompat.STATE_PAUSED,
getSongProgressMillis(), getSongProgressMillis(),
1); PreferenceUtil.INSTANCE.getPlaybackSpeed());
setCustomAction(stateBuilder); setCustomAction(stateBuilder);

View file

@ -674,6 +674,16 @@ object PreferenceUtil {
} else { } else {
LyricsType.OVER_LYRICS LyricsType.OVER_LYRICS
} }
var playbackSpeed
get() = sharedPreferences
.getFloat(PLAYBACK_SPEED, 1F)
set(value) = sharedPreferences.edit { putFloat(PLAYBACK_SPEED, value) }
var playbackPitch
get() = sharedPreferences
.getFloat(PLAYBACK_PITCH, 1F)
set(value) = sharedPreferences.edit { putFloat(PLAYBACK_PITCH, value) }
} }
enum class LyricsType { enum class LyricsType {
REPLACE_LYRICS, OVER_LYRICS REPLACE_LYRICS, OVER_LYRICS

View file

@ -0,0 +1,12 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:pathData="M4.027,13.011H2.014C2.201,14.887 2.914,16.672 4.071,18.158L5.504,16.718C4.706,15.628 4.197,14.352 4.027,13.011ZM22,12.002C21.996,9.511 21.073,7.111 19.407,5.264C17.741,3.417 15.451,2.254 12.98,2V4.035C15.581,4.366 17.856,5.953 19.071,8.283C20.285,10.613 20.285,13.391 19.071,15.72C17.856,18.05 15.581,19.637 12.98,19.968V21.986C15.448,21.733 17.736,20.573 19.402,18.73C21.067,16.886 21.993,14.49 22,12.002ZM6.869,18.192L5.447,19.618C6.999,20.964 8.925,21.796 10.966,22V19.981C9.458,19.789 8.036,19.168 6.869,18.192ZM5.49,7.282L4.057,5.846C2.9,7.332 2.187,9.117 2,10.993H4.014C4.184,9.65 4.692,8.373 5.49,7.282ZM10.966,4.036V2C8.926,2.206 6.998,3.038 5.446,4.382L6.869,5.808C8.038,4.838 9.46,4.223 10.966,4.036Z"
android:fillColor="#fff"/>
<path
android:pathData="M15.6432,11.152C16.2699,11.5437 16.2699,12.4563 15.6432,12.848L10.53,16.0438C9.8639,16.46 9,15.9812 9,15.1958V8.8042C9,8.0188 9.8639,7.54 10.53,7.9563L15.6432,11.152Z"
android:fillColor="#fff"/>
</vector>

View file

@ -0,0 +1,82 @@
<?xml version="1.0" encoding="utf-8"?><!--
~ Copyright (c) 2019 Hemanth Savarala.
~
~ Licensed under the GNU General Public License v3
~
~ This is free software: you can redistribute it and/or modify it under
~ the terms of the GNU General Public License as published by
~ the Free Software Foundation either version 3 of the License, or (at your option) any later version.
~
~ This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
~ without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
~ See the GNU General Public License for more details.
-->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:paddingHorizontal="16dp"
android:paddingVertical="16dp">
<TextView
android:id="@+id/playback_speed_title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="3"
android:text="@string/playback_speed"
android:textAppearance="@style/TextViewBody1" />
<com.google.android.material.slider.Slider
android:id="@+id/playback_speed_slider"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="3"
android:stepSize="0.05"
android:valueFrom="0.25"
android:valueTo="4.00"
app:labelBehavior="gone"
app:tickVisible="false" />
<TextView
android:id="@+id/speed_value"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:layout_weight="1"
android:gravity="center"
android:textAppearance="@style/TextViewBody1"
tools:text="1.00" />
<TextView
android:id="@+id/playback_pitch_title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:layout_weight="3"
android:text="@string/playback_pitch"
android:textAppearance="@style/TextViewBody1" />
<com.google.android.material.slider.Slider
android:id="@+id/playback_pitch_slider"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="3"
android:stepSize="0.05"
android:valueFrom="0.50"
android:valueTo="2.00"
app:labelBehavior="gone"
app:tickVisible="false" />
<TextView
android:id="@+id/pitch_value"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:layout_weight="1"
android:gravity="center"
android:textAppearance="@style/TextViewBody1"
tools:text="1.00" />
</LinearLayout>

View file

@ -1,6 +1,15 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android" <menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"> xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools">
<item
android:id="@+id/action_playback_speed"
android:checkable="true"
android:icon="@drawable/ic_playback_speed"
android:orderInCategory="0"
android:title="@string/playback_speed"
app:showAsAction="always"
tools:targetApi="23" />
<item <item
android:id="@+id/action_toggle_lyrics" android:id="@+id/action_toggle_lyrics"
android:checkable="true" android:checkable="true"

View file

@ -526,4 +526,7 @@
<string name="you_have_to_select_at_least_one_category">You have to select at least one category.</string> <string name="you_have_to_select_at_least_one_category">You have to select at least one category.</string>
<string name="you_will_be_forwarded_to_the_issue_tracker_website">You will be forwarded to the issue tracker website.</string> <string name="you_will_be_forwarded_to_the_issue_tracker_website">You will be forwarded to the issue tracker website.</string>
<string name="your_account_data_is_only_used_for_authentication">Your account data is only used for authentication.</string> <string name="your_account_data_is_only_used_for_authentication">Your account data is only used for authentication.</string>
<string name="playback_speed">Playback Speed</string>
<string name="playback_pitch">Pitch</string>
<string name="playback_settings">Playback Settings</string>
</resources> </resources>