4.5.4 commit

This commit is contained in:
Xilin Jia 2024-04-04 17:30:30 +00:00
parent 15321d51e5
commit 9233a443de
17 changed files with 117 additions and 53 deletions

View File

@ -149,8 +149,8 @@ android {
// Version code schema (not used): // Version code schema (not used):
// "1.2.3-beta4" -> 1020304 // "1.2.3-beta4" -> 1020304
// "1.2.3" -> 1020395 // "1.2.3" -> 1020395
versionCode 3020123 versionCode 3020124
versionName "4.5.3" versionName "4.5.4"
def commit = "" def commit = ""
try { try {

View File

@ -31,6 +31,8 @@ class TypeGetter {
reader = createReader(feed) reader = createReader(feed)
xpp.setInput(reader) xpp.setInput(reader)
var eventType = xpp.eventType var eventType = xpp.eventType
// TODO: need to check about handling webpage
// return return Type.ATOM
while (eventType != XmlPullParser.END_DOCUMENT) { while (eventType != XmlPullParser.END_DOCUMENT) {
if (eventType == XmlPullParser.START_TAG) { if (eventType == XmlPullParser.START_TAG) {
@ -63,7 +65,9 @@ class TypeGetter {
Log.d(TAG, "Recognized type RSS 0.91/0.92") Log.d(TAG, "Recognized type RSS 0.91/0.92")
return Type.RSS091 return Type.RSS091
} }
else -> throw UnsupportedFeedtypeException("Unsupported rss version") else -> {
throw UnsupportedFeedtypeException("Unsupported rss version")
}
} }
} }
else -> { else -> {

View File

@ -26,8 +26,7 @@ object PlaybackSpeedUtils {
mediaType = media.getMediaType() mediaType = media.getMediaType()
playbackSpeed = PlaybackPreferences.currentlyPlayingTemporaryPlaybackSpeed playbackSpeed = PlaybackPreferences.currentlyPlayingTemporaryPlaybackSpeed
// if (playbackSpeed == FeedPreferences.SPEED_USE_GLOBAL && media is FeedMedia) { if (playbackSpeed == FeedPreferences.SPEED_USE_GLOBAL && media is FeedMedia) {
if (media is FeedMedia) {
val item = media.item val item = media.item
if (item != null) { if (item != null) {
val feed = item.feed val feed = item.feed

View File

@ -13,6 +13,7 @@ import ac.mdiq.podcini.storage.model.feed.FeedMedia
import ac.mdiq.podcini.storage.model.playback.MediaType import ac.mdiq.podcini.storage.model.playback.MediaType
import ac.mdiq.podcini.storage.model.playback.Playable import ac.mdiq.podcini.storage.model.playback.Playable
import ac.mdiq.podcini.playback.base.PlayerStatus import ac.mdiq.podcini.playback.base.PlayerStatus
import ac.mdiq.podcini.preferences.UserPreferences
import android.content.* import android.content.*
import android.os.Build import android.os.Build
import android.os.IBinder import android.os.IBinder
@ -152,6 +153,7 @@ abstract class PlaybackController(private val activity: FragmentActivity) {
override fun onServiceConnected(className: ComponentName, service: IBinder) { override fun onServiceConnected(className: ComponentName, service: IBinder) {
if (service is LocalBinder) { if (service is LocalBinder) {
playbackService = service.service playbackService = service.service
onPlaybackServiceConnected()
if (!released) { if (!released) {
queryService() queryService()
Log.d(TAG, "Connection to Service established") Log.d(TAG, "Connection to Service established")
@ -243,6 +245,8 @@ abstract class PlaybackController(private val activity: FragmentActivity) {
abstract fun loadMediaInfo() abstract fun loadMediaInfo()
open fun onPlaybackServiceConnected() { }
/** /**
* Called when connection to playback service has been established or * Called when connection to playback service has been established or
* information has to be refreshed * information has to be refreshed
@ -342,10 +346,11 @@ abstract class PlaybackController(private val activity: FragmentActivity) {
playbackService?.setVideoSurface(holder) playbackService?.setVideoSurface(holder)
} }
fun setPlaybackSpeed(speed: Float, codeArray: Array<Int>? = null) { fun setPlaybackSpeed(speed: Float, codeArray: BooleanArray? = null) {
if (playbackService != null) { if (playbackService != null) {
playbackService!!.setSpeed(speed, codeArray) playbackService!!.setSpeed(speed, codeArray)
} else { } else {
UserPreferences.setPlaybackSpeed(speed)
EventBus.getDefault().post(SpeedChangedEvent(speed)) EventBus.getDefault().post(SpeedChangedEvent(speed))
} }
} }

View File

@ -1617,18 +1617,18 @@ class PlaybackService : MediaBrowserServiceCompat() {
val playable: Playable? val playable: Playable?
get() = mediaPlayer?.getPlayable() get() = mediaPlayer?.getPlayable()
fun setSpeed(speed: Float, codeArray: Array<Int>? = null) { fun setSpeed(speed: Float, codeArray: BooleanArray? = null) {
isSpeedForward = false isSpeedForward = false
isFallbackSpeed = false isFallbackSpeed = false
currentlyPlayingTemporaryPlaybackSpeed = speed
if (currentMediaType == MediaType.VIDEO) { if (currentMediaType == MediaType.VIDEO) {
currentlyPlayingTemporaryPlaybackSpeed = speed
videoPlaybackSpeed = speed videoPlaybackSpeed = speed
mediaPlayer?.setPlaybackParams(speed, isSkipSilence)
} else { } else {
if (codeArray != null && codeArray.size == 3) { if (codeArray != null && codeArray.size == 3) {
if (codeArray[2] == 1) setPlaybackSpeed(speed) if (codeArray[2]) setPlaybackSpeed(speed)
if (codeArray[1] == 1 && playable is FeedMedia) { if (codeArray[1] && playable is FeedMedia) {
var item = (playable as FeedMedia).item var item = (playable as FeedMedia).item
if (item == null) { if (item == null) {
val itemId = (playable as FeedMedia).itemId val itemId = (playable as FeedMedia).itemId
@ -1651,11 +1651,16 @@ class PlaybackService : MediaBrowserServiceCompat() {
} }
} }
} }
} if (codeArray[0]) {
} currentlyPlayingTemporaryPlaybackSpeed = speed
mediaPlayer?.setPlaybackParams(speed, isSkipSilence) mediaPlayer?.setPlaybackParams(speed, isSkipSilence)
} }
} else {
currentlyPlayingTemporaryPlaybackSpeed = speed
mediaPlayer?.setPlaybackParams(speed, isSkipSilence)
}
}
}
fun speedForward(speed: Float) { fun speedForward(speed: Float) {
if (mediaPlayer == null || isFallbackSpeed) return if (mediaPlayer == null || isFallbackSpeed) return

View File

@ -11,8 +11,8 @@ class PlaybackSpeedDialogActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
setTheme(getTranslucentTheme(this)) setTheme(getTranslucentTheme(this))
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
val speedDialog: VariableSpeedDialog = InnerVariableSpeedDialog() val speedDialog: VariableSpeedDialog? = VariableSpeedDialog.newInstance(booleanArrayOf(false, false, true), 2)
speedDialog.show(supportFragmentManager, null) speedDialog?.show(supportFragmentManager, null)
} }
class InnerVariableSpeedDialog : VariableSpeedDialog() { class InnerVariableSpeedDialog : VariableSpeedDialog() {

View File

@ -609,7 +609,7 @@ class VideoplayerActivity : CastEnabledActivity(), OnSeekBarChangeListener {
shareDialog.show(supportFragmentManager, "ShareEpisodeDialog") shareDialog.show(supportFragmentManager, "ShareEpisodeDialog")
} }
item.itemId == R.id.playback_speed -> { item.itemId == R.id.playback_speed -> {
VariableSpeedDialog().show(supportFragmentManager, null) VariableSpeedDialog.newInstance(booleanArrayOf(true, false, true))?.show(supportFragmentManager, null)
} }
else -> { else -> {
return false return false

View File

@ -12,6 +12,7 @@ import android.view.LayoutInflater
import androidx.media3.common.util.UnstableApi import androidx.media3.common.util.UnstableApi
import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.google.android.material.dialog.MaterialAlertDialogBuilder
import java.lang.ref.WeakReference import java.lang.ref.WeakReference
import kotlin.math.round
@UnstableApi @UnstableApi
class EditFallbackSpeedDialog(activity: Activity) { class EditFallbackSpeedDialog(activity: Activity) {
@ -32,7 +33,7 @@ import java.lang.ref.WeakReference
speed < 0.0f -> speed = 0.0f speed < 0.0f -> speed = 0.0f
speed > 3.0f -> speed = 3.0f speed > 3.0f -> speed = 3.0f
} }
fallbackSpeed = String.format("%.2f", speed).toFloat() fallbackSpeed = round(100 * speed) / 100
} }
.setNegativeButton(R.string.cancel_label, null) .setNegativeButton(R.string.cancel_label, null)
.show() .show()

View File

@ -12,6 +12,7 @@ import android.view.LayoutInflater
import androidx.media3.common.util.UnstableApi import androidx.media3.common.util.UnstableApi
import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.google.android.material.dialog.MaterialAlertDialogBuilder
import java.lang.ref.WeakReference import java.lang.ref.WeakReference
import kotlin.math.round
@UnstableApi @UnstableApi
class EditForwardSpeedDialog(activity: Activity) { class EditForwardSpeedDialog(activity: Activity) {
@ -33,7 +34,7 @@ import java.lang.ref.WeakReference
speed < 0.0f -> speed = 0.0f speed < 0.0f -> speed = 0.0f
speed > 10.0f -> speed = 10.0f speed > 10.0f -> speed = 10.0f
} }
speedforwardSpeed = String.format("%.1f", speed).toFloat() speedforwardSpeed = round(10 * speed) / 10
} }
.setNegativeButton(R.string.cancel_label, null) .setNegativeButton(R.string.cancel_label, null)
.show() .show()

View File

@ -11,6 +11,7 @@ import ac.mdiq.podcini.ui.view.PlaybackSpeedSeekBar
import android.os.Bundle import android.os.Bundle
import android.os.Handler import android.os.Handler
import android.os.Looper import android.os.Looper
import android.util.Log
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
@ -40,7 +41,7 @@ open class VariableSpeedDialog : BottomSheetDialogFragment() {
private var controller: PlaybackController? = null private var controller: PlaybackController? = null
private val selectedSpeeds: MutableList<Float> private val selectedSpeeds: MutableList<Float>
private val settingCode: Array<Int> = Array(3) { 0 } private lateinit var settingCode: BooleanArray
init { init {
val format = DecimalFormatSymbols(Locale.US) val format = DecimalFormatSymbols(Locale.US)
@ -50,14 +51,23 @@ open class VariableSpeedDialog : BottomSheetDialogFragment() {
@UnstableApi override fun onStart() { @UnstableApi override fun onStart() {
super.onStart() super.onStart()
if (controller == null) {
controller = object : PlaybackController(requireActivity()) { controller = object : PlaybackController(requireActivity()) {
override fun loadMediaInfo() { override fun loadMediaInfo() {
if (controller != null) updateSpeed(SpeedChangedEvent(controller!!.currentPlaybackSpeedMultiplier)) if (controller != null) updateSpeed(SpeedChangedEvent(controller!!.currentPlaybackSpeedMultiplier))
} }
override fun onPlaybackServiceConnected() {
super.onPlaybackServiceConnected()
binding.currentAudio.visibility = View.VISIBLE
binding.currentPodcast.visibility = View.VISIBLE
if (!settingCode[0]) binding.currentAudio.visibility = View.INVISIBLE
if (!settingCode[1]) binding.currentPodcast.visibility = View.INVISIBLE
if (!settingCode[2]) binding.global.visibility = View.INVISIBLE
}
} }
controller?.init() controller?.init()
}
EventBus.getDefault().register(this) EventBus.getDefault().register(this)
if (controller != null) updateSpeed(SpeedChangedEvent(controller!!.currentPlaybackSpeedMultiplier)) if (controller != null) updateSpeed(SpeedChangedEvent(controller!!.currentPlaybackSpeedMultiplier))
} }
@ -80,23 +90,28 @@ open class VariableSpeedDialog : BottomSheetDialogFragment() {
): View? { ): View? {
_binding = SpeedSelectDialogBinding.inflate(inflater) _binding = SpeedSelectDialogBinding.inflate(inflater)
val argument = arguments?.getString("default_setting") settingCode = (arguments?.getBooleanArray("settingCode") ?: BooleanArray(3) {true})
val index_default = arguments?.getInt("index_default")
when (argument) { when (index_default) {
null, "Current" -> { null, 0 -> {
binding.currentAudio.isChecked = true binding.currentAudio.isChecked = true
} }
"Feed" -> { 1 -> {
binding.currentPodcast.isChecked = true binding.currentPodcast.isChecked = true
} }
else -> { else -> {
binding.global.isChecked = true binding.global.isChecked = true
} }
} }
// if (!settingCode[0]) binding.currentAudio.visibility = View.INVISIBLE
// if (!settingCode[1]) binding.currentPodcast.visibility = View.INVISIBLE
// if (!settingCode[2]) binding.global.visibility = View.INVISIBLE
speedSeekBar = binding.speedSeekBar speedSeekBar = binding.speedSeekBar
speedSeekBar.setProgressChangedListener { multiplier: Float -> speedSeekBar.setProgressChangedListener { multiplier: Float ->
controller?.setPlaybackSpeed(multiplier) addCurrentSpeedChip.text = String.format(Locale.getDefault(), "%1$.2f", multiplier)
// controller?.setPlaybackSpeed(multiplier)
} }
val selectedSpeedsGrid = binding.selectedSpeedsGrid val selectedSpeedsGrid = binding.selectedSpeedsGrid
selectedSpeedsGrid.layoutManager = GridLayoutManager(context, 3) selectedSpeedsGrid.layoutManager = GridLayoutManager(context, 3)
@ -124,9 +139,13 @@ open class VariableSpeedDialog : BottomSheetDialogFragment() {
@OptIn(UnstableApi::class) override fun onViewCreated(view: View, savedInstanceState: Bundle?) { @OptIn(UnstableApi::class) override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
// if (controller == null || !controller!!.isPlaybackServiceReady()) { if (controller == null || !controller!!.isPlaybackServiceReady()) {
// binding.currentPodcast.visibility = View.INVISIBLE binding.currentAudio.visibility = View.INVISIBLE
// } else binding.currentPodcast.visibility = View.VISIBLE binding.currentPodcast.visibility = View.INVISIBLE
} else {
binding.currentAudio.visibility = View.VISIBLE
binding.currentPodcast.visibility = View.VISIBLE
}
} }
override fun onDestroyView() { override fun onDestroyView() {
@ -165,9 +184,9 @@ open class VariableSpeedDialog : BottomSheetDialogFragment() {
true true
} }
holder.chip.setOnClickListener { Handler(Looper.getMainLooper()).postDelayed({ holder.chip.setOnClickListener { Handler(Looper.getMainLooper()).postDelayed({
if (binding.currentAudio.isChecked) settingCode[0] = 1 if (binding.currentAudio.isChecked) settingCode[0] = true
if (binding.currentPodcast.isChecked) settingCode[1] = 1 if (binding.currentPodcast.isChecked) settingCode[1] = true
if (binding.global.isChecked) settingCode[2] = 1 if (binding.global.isChecked) settingCode[2] = true
if (controller != null) { if (controller != null) {
dismiss() dismiss()
@ -189,13 +208,18 @@ open class VariableSpeedDialog : BottomSheetDialogFragment() {
} }
companion object { companion object {
fun newInstance(argument: String? = null): VariableSpeedDialog { fun newInstance(settingCode_: BooleanArray? = null, index_default: Int? = null): VariableSpeedDialog? {
val dialog = VariableSpeedDialog() val settingCode = settingCode_ ?: BooleanArray(3){false}
if (argument != null) { if (settingCode.size != 3) {
val args = Bundle() Log.e("VariableSpeedDialog", "wrong settingCode dimension")
args.putString("default_setting", argument) return null
dialog.arguments = args
} }
val dialog = VariableSpeedDialog()
val args = Bundle()
args.putBooleanArray("settingCode", settingCode)
if (index_default != null) args.putInt("index_default", index_default)
dialog.arguments = args
return dialog return dialog
} }
} }

View File

@ -500,7 +500,7 @@ class AudioPlayerFragment : Fragment(), SeekBar.OnSeekBarChangeListener, Toolbar
setupLengthTextView() setupLengthTextView()
setupControlButtons() setupControlButtons()
butPlaybackSpeed.setOnClickListener { butPlaybackSpeed.setOnClickListener {
VariableSpeedDialog.newInstance(null).show(childFragmentManager, null) VariableSpeedDialog.newInstance(booleanArrayOf(true, true, true), null)?.show(childFragmentManager, null)
} }
sbPosition.setOnSeekBarChangeListener(this) sbPosition.setOnSeekBarChangeListener(this)

View File

@ -64,6 +64,7 @@ import io.reactivex.schedulers.Schedulers
import org.greenrobot.eventbus.EventBus import org.greenrobot.eventbus.EventBus
import org.greenrobot.eventbus.Subscribe import org.greenrobot.eventbus.Subscribe
import org.greenrobot.eventbus.ThreadMode import org.greenrobot.eventbus.ThreadMode
import org.jsoup.Jsoup
import java.io.File import java.io.File
import java.io.IOException import java.io.IOException
import kotlin.concurrent.Volatile import kotlin.concurrent.Volatile
@ -330,6 +331,16 @@ class OnlineFeedViewFragment : Fragment() {
Log.d(TAG, "Unsupported feed type detected") Log.d(TAG, "Unsupported feed type detected")
if ("html".equals(e.rootElement, ignoreCase = true)) { if ("html".equals(e.rootElement, ignoreCase = true)) {
if (selectedDownloadUrl != null) { if (selectedDownloadUrl != null) {
val doc = Jsoup.connect(selectedDownloadUrl).get()
val linkElements = doc.select("link[type=application/rss+xml]")
for (element in linkElements) {
val rssUrl = element.attr("href")
Log.d(TAG, "RSS URL: $rssUrl")
val rc = destinationFile.delete()
Log.d(TAG, "Deleted feed source file. Result: $rc")
startFeedDownload(rssUrl)
return null
}
val dialogShown = showFeedDiscoveryDialog(destinationFile, selectedDownloadUrl!!) val dialogShown = showFeedDiscoveryDialog(destinationFile, selectedDownloadUrl!!)
if (dialogShown) { if (dialogShown) {
null // Should not display an error message null // Should not display an error message

View File

@ -39,7 +39,7 @@ class PlaybackPreferencesFragment : PreferenceFragmentCompat() {
findPreference<Preference>(PREF_PLAYBACK_SPEED_LAUNCHER)!!.onPreferenceClickListener = findPreference<Preference>(PREF_PLAYBACK_SPEED_LAUNCHER)!!.onPreferenceClickListener =
Preference.OnPreferenceClickListener { Preference.OnPreferenceClickListener {
VariableSpeedDialog.newInstance("Global").show(childFragmentManager, null) VariableSpeedDialog.newInstance(booleanArrayOf(false, false, true),2)?.show(childFragmentManager, null)
true true
} }
findPreference<Preference>(PREF_PLAYBACK_REWIND_DELTA_LAUNCHER)!!.onPreferenceClickListener = findPreference<Preference>(PREF_PLAYBACK_REWIND_DELTA_LAUNCHER)!!.onPreferenceClickListener =

View File

@ -64,12 +64,12 @@
<LinearLayout <LinearLayout
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="75dp" android:layout_height="80dp"
android:orientation="horizontal"> android:orientation="horizontal">
<ImageView <ImageView
android:id="@+id/imgvCover" android:id="@+id/imgvCover"
android:layout_width="75dp" android:layout_width="80dp"
android:layout_height="match_parent" android:layout_height="match_parent"
android:cropToPadding="true" android:cropToPadding="true"
android:scaleType="fitCenter" android:scaleType="fitCenter"
@ -127,9 +127,8 @@
android:layout_alignLeft="@id/butRev" android:layout_alignLeft="@id/butRev"
android:layout_alignEnd="@id/butRev" android:layout_alignEnd="@id/butRev"
android:layout_alignRight="@id/butRev" android:layout_alignRight="@id/butRev"
android:layout_marginBottom="5dp"
android:clickable="false" android:clickable="false"
android:gravity="center" android:gravity="center_horizontal"
android:text="30" android:text="30"
android:textColor="?android:attr/textColorSecondary" android:textColor="?android:attr/textColorSecondary"
android:textSize="12sp" /> android:textSize="12sp" />
@ -158,7 +157,6 @@
android:layout_alignLeft="@id/butPlaybackSpeed" android:layout_alignLeft="@id/butPlaybackSpeed"
android:layout_alignEnd="@id/butPlaybackSpeed" android:layout_alignEnd="@id/butPlaybackSpeed"
android:layout_alignRight="@id/butPlaybackSpeed" android:layout_alignRight="@id/butPlaybackSpeed"
android:layout_marginBottom="5dp"
android:clickable="false" android:clickable="false"
android:gravity="center" android:gravity="center"
android:text="1.00" android:text="1.00"
@ -191,7 +189,6 @@
android:layout_alignLeft="@id/butFF" android:layout_alignLeft="@id/butFF"
android:layout_alignEnd="@id/butFF" android:layout_alignEnd="@id/butFF"
android:layout_alignRight="@id/butFF" android:layout_alignRight="@id/butFF"
android:layout_marginBottom="5dp"
android:clickable="false" android:clickable="false"
android:gravity="center" android:gravity="center"
android:text="30" android:text="30"
@ -224,7 +221,6 @@
android:layout_alignLeft="@id/butSkip" android:layout_alignLeft="@id/butSkip"
android:layout_alignEnd="@id/butSkip" android:layout_alignEnd="@id/butSkip"
android:layout_alignRight="@id/butSkip" android:layout_alignRight="@id/butSkip"
android:layout_marginBottom="5dp"
android:clickable="false" android:clickable="false"
android:gravity="center" android:gravity="center"
android:textColor="?android:attr/textColorSecondary" android:textColor="?android:attr/textColorSecondary"

View File

@ -5,7 +5,7 @@
<dimen name="drawer_corner_size">16dp</dimen> <dimen name="drawer_corner_size">16dp</dimen>
<dimen name="widget_margin">0dp</dimen> <dimen name="widget_margin">0dp</dimen>
<dimen name="widget_inner_radius">4dp</dimen> <dimen name="widget_inner_radius">4dp</dimen>
<dimen name="external_player_height">130dp</dimen> <dimen name="external_player_height">135dp</dimen>
<dimen name="text_size_micro">12sp</dimen> <dimen name="text_size_micro">12sp</dimen>
<dimen name="text_size_small">14sp</dimen> <dimen name="text_size_small">14sp</dimen>
<dimen name="text_size_navdrawer">16sp</dimen> <dimen name="text_size_navdrawer">16sp</dimen>

View File

@ -216,3 +216,12 @@
* adjusted layout and button dimension and alignments in the bottom player control * adjusted layout and button dimension and alignments in the bottom player control
* fallback speed setting is now capped at 0.0 and 3.0 and allows for 2-digit precision * fallback speed setting is now capped at 0.0 and 3.0 and allows for 2-digit precision
* corrected episode count display in subscriptions list when the feed has 0 episodes * corrected episode count display in subscriptions list when the feed has 0 episodes
## 4.5.4
* fixed crash bug when setting fallback or fast-forward speeds with some Locales
* further enlarged height of the bottom player control to improve on missing pixels at the bottom
* on speed setting dialog, only tap on a preset chip sets the speed, only selected options will be set
* corrected the handling of current audio speed:
* when speed for current audio is not set, podcast speed takes precedence
* when speed for current audio is set, it takes precedence for the current audio (episode), even with pause/re-play

View File

@ -0,0 +1,9 @@
Version 4.5.3 brings several changes:
* fixed crash bug when setting fallback or fast-forward speeds with some Locales
* further enlarged height of the bottom player control to improve on missing pixels at the bottom
* on speed setting dialog, only tap on a preset chip sets the speed, only selected options will be set
* corrected the handling of current audio speed:
* when speed for current audio is not set, podcast speed takes precedence
* when speed for current audio is set, it takes precedence for the current audio (episode), even with pause/re-play