From 23c90f4118a98fa190b36eede66e8ff70f615487 Mon Sep 17 00:00:00 2001 From: Alex Date: Sun, 5 Feb 2023 19:38:12 -0600 Subject: [PATCH 1/5] Add playback spped adjustment for Android Auto --- .../audiobookshelf/app/media/MediaManager.kt | 28 +++++++++++++++ .../app/player/MediaSessionCallback.kt | 19 ++++++++++ .../app/player/PlayerConstants.kt | 1 + .../app/player/PlayerNotificationService.kt | 33 ++++++++++++++++++ .../drawable-anydpi/ic_play_speed_0_5x.xml | 25 +++++++++++++ .../drawable-anydpi/ic_play_speed_1_0x.xml | 27 ++++++++++++++ .../drawable-anydpi/ic_play_speed_1_2x.xml | 27 ++++++++++++++ .../drawable-anydpi/ic_play_speed_1_5x.xml | 27 ++++++++++++++ .../drawable-anydpi/ic_play_speed_2_0x.xml | 25 +++++++++++++ .../drawable-anydpi/ic_play_speed_3_0x.xml | 25 +++++++++++++ .../res/drawable-hdpi/ic_play_speed_0_5x.png | Bin 0 -> 500 bytes .../res/drawable-hdpi/ic_play_speed_1_0x.png | Bin 0 -> 447 bytes .../res/drawable-hdpi/ic_play_speed_1_2x.png | Bin 0 -> 450 bytes .../res/drawable-hdpi/ic_play_speed_1_5x.png | Bin 0 -> 465 bytes .../res/drawable-hdpi/ic_play_speed_2_0x.png | Bin 0 -> 512 bytes .../res/drawable-hdpi/ic_play_speed_3_0x.png | Bin 0 -> 496 bytes .../res/drawable-mdpi/ic_play_speed_0_5x.png | Bin 0 -> 323 bytes .../res/drawable-mdpi/ic_play_speed_1_0x.png | Bin 0 -> 311 bytes .../res/drawable-mdpi/ic_play_speed_1_2x.png | Bin 0 -> 303 bytes .../res/drawable-mdpi/ic_play_speed_1_5x.png | Bin 0 -> 315 bytes .../res/drawable-mdpi/ic_play_speed_2_0x.png | Bin 0 -> 338 bytes .../res/drawable-mdpi/ic_play_speed_3_0x.png | Bin 0 -> 346 bytes .../res/drawable-xhdpi/ic_play_speed_0_5x.png | Bin 0 -> 664 bytes .../res/drawable-xhdpi/ic_play_speed_1_0x.png | Bin 0 -> 608 bytes .../res/drawable-xhdpi/ic_play_speed_1_2x.png | Bin 0 -> 583 bytes .../res/drawable-xhdpi/ic_play_speed_1_5x.png | Bin 0 -> 599 bytes .../res/drawable-xhdpi/ic_play_speed_2_0x.png | Bin 0 -> 684 bytes .../res/drawable-xhdpi/ic_play_speed_3_0x.png | Bin 0 -> 712 bytes .../drawable-xxhdpi/ic_play_speed_0_5x.png | Bin 0 -> 1013 bytes .../drawable-xxhdpi/ic_play_speed_1_0x.png | Bin 0 -> 895 bytes .../drawable-xxhdpi/ic_play_speed_1_2x.png | Bin 0 -> 866 bytes .../drawable-xxhdpi/ic_play_speed_1_5x.png | Bin 0 -> 892 bytes .../drawable-xxhdpi/ic_play_speed_2_0x.png | Bin 0 -> 1011 bytes .../drawable-xxhdpi/ic_play_speed_3_0x.png | Bin 0 -> 1049 bytes 34 files changed, 237 insertions(+) create mode 100644 android/app/src/main/res/drawable-anydpi/ic_play_speed_0_5x.xml create mode 100644 android/app/src/main/res/drawable-anydpi/ic_play_speed_1_0x.xml create mode 100644 android/app/src/main/res/drawable-anydpi/ic_play_speed_1_2x.xml create mode 100644 android/app/src/main/res/drawable-anydpi/ic_play_speed_1_5x.xml create mode 100644 android/app/src/main/res/drawable-anydpi/ic_play_speed_2_0x.xml create mode 100644 android/app/src/main/res/drawable-anydpi/ic_play_speed_3_0x.xml create mode 100644 android/app/src/main/res/drawable-hdpi/ic_play_speed_0_5x.png create mode 100644 android/app/src/main/res/drawable-hdpi/ic_play_speed_1_0x.png create mode 100644 android/app/src/main/res/drawable-hdpi/ic_play_speed_1_2x.png create mode 100644 android/app/src/main/res/drawable-hdpi/ic_play_speed_1_5x.png create mode 100644 android/app/src/main/res/drawable-hdpi/ic_play_speed_2_0x.png create mode 100644 android/app/src/main/res/drawable-hdpi/ic_play_speed_3_0x.png create mode 100644 android/app/src/main/res/drawable-mdpi/ic_play_speed_0_5x.png create mode 100644 android/app/src/main/res/drawable-mdpi/ic_play_speed_1_0x.png create mode 100644 android/app/src/main/res/drawable-mdpi/ic_play_speed_1_2x.png create mode 100644 android/app/src/main/res/drawable-mdpi/ic_play_speed_1_5x.png create mode 100644 android/app/src/main/res/drawable-mdpi/ic_play_speed_2_0x.png create mode 100644 android/app/src/main/res/drawable-mdpi/ic_play_speed_3_0x.png create mode 100644 android/app/src/main/res/drawable-xhdpi/ic_play_speed_0_5x.png create mode 100644 android/app/src/main/res/drawable-xhdpi/ic_play_speed_1_0x.png create mode 100644 android/app/src/main/res/drawable-xhdpi/ic_play_speed_1_2x.png create mode 100644 android/app/src/main/res/drawable-xhdpi/ic_play_speed_1_5x.png create mode 100644 android/app/src/main/res/drawable-xhdpi/ic_play_speed_2_0x.png create mode 100644 android/app/src/main/res/drawable-xhdpi/ic_play_speed_3_0x.png create mode 100644 android/app/src/main/res/drawable-xxhdpi/ic_play_speed_0_5x.png create mode 100644 android/app/src/main/res/drawable-xxhdpi/ic_play_speed_1_0x.png create mode 100644 android/app/src/main/res/drawable-xxhdpi/ic_play_speed_1_2x.png create mode 100644 android/app/src/main/res/drawable-xxhdpi/ic_play_speed_1_5x.png create mode 100644 android/app/src/main/res/drawable-xxhdpi/ic_play_speed_2_0x.png create mode 100644 android/app/src/main/res/drawable-xxhdpi/ic_play_speed_3_0x.png diff --git a/android/app/src/main/java/com/audiobookshelf/app/media/MediaManager.kt b/android/app/src/main/java/com/audiobookshelf/app/media/MediaManager.kt index 9d463547..c5715ebe 100644 --- a/android/app/src/main/java/com/audiobookshelf/app/media/MediaManager.kt +++ b/android/app/src/main/java/com/audiobookshelf/app/media/MediaManager.kt @@ -12,6 +12,7 @@ import java.util.* import kotlinx.coroutines.coroutineScope import kotlinx.coroutines.runBlocking import org.json.JSONException +import org.json.JSONObject import kotlin.coroutines.resume import kotlin.coroutines.suspendCoroutine @@ -61,6 +62,33 @@ class MediaManager(private var apiHandler: ApiHandler, var ctx: Context) { return 1f } + fun setSavedPlaybackRate(newRate: Float) { + val sharedPrefs = ctx.getSharedPreferences("CapacitorStorage", Activity.MODE_PRIVATE) + val sharedPrefEditor = sharedPrefs.edit() + if (sharedPrefs != null) { + val userSettingsPref = sharedPrefs.getString("userSettings", null) + if (userSettingsPref != null) { + try { + val userSettings = JSObject(userSettingsPref) + userSettings.put("playbackRate", newRate.toDouble()) + sharedPrefEditor.putString("userSettings", userSettings.toString()) + sharedPrefEditor.commit() + userSettingsPlaybackRate = newRate + Log.d(tag, "Saved userSettings JSON from Android Auto") + } catch(je:JSONException) { + Log.e(tag, "Failed to save userSettings JSON ${je.localizedMessage}") + } + } else { + // Not sure if this is the best place for this, but if a user has not changed any user settings in the app + // the object will not exist yet, could be moved to a centralized place or created on first app load + var userSettings = JSONObject() + userSettings.put("playbackRate", newRate.toDouble()) + sharedPrefEditor.putString("userSettings", userSettings.toString()) + Log.d(tag, "Created and saved userSettings JSON from Android Auto") + } + } + } + fun checkResetServerItems() { // When opening android auto need to check if still connected to server // and reset any server data already set diff --git a/android/app/src/main/java/com/audiobookshelf/app/player/MediaSessionCallback.kt b/android/app/src/main/java/com/audiobookshelf/app/player/MediaSessionCallback.kt index 0dd7b2cb..bdddcae8 100644 --- a/android/app/src/main/java/com/audiobookshelf/app/player/MediaSessionCallback.kt +++ b/android/app/src/main/java/com/audiobookshelf/app/player/MediaSessionCallback.kt @@ -88,6 +88,24 @@ class MediaSessionCallback(var playerNotificationService:PlayerNotificationServi playerNotificationService.seekPlayer(pos) } + private fun onChangeSpeed() { + // cycle to next speed, only contains preset android app options, as each increment needs it's own icon + var mediaManager = playerNotificationService.mediaManager + var currentSpeed = mediaManager.getSavedPlaybackRate() + var newSpeed = when (currentSpeed) { + 0.5f -> 1.0f + 1.0f -> 1.2f + 1.2f -> 1.5f + 1.5f -> 2.0f + 2.0f -> 3.0f + 3.0f -> 0.5f + // anything set above 3 (can happen in the android app) will be reset to 1 + else -> 1.0f + } + mediaManager.setSavedPlaybackRate(newSpeed) + playerNotificationService.setPlaybackSpeed(newSpeed) + } + override fun onPlayFromMediaId(mediaId: String?, extras: Bundle?) { Log.d(tag, "ON PLAY FROM MEDIA ID $mediaId") val libraryItemWrapper: LibraryItemWrapper? @@ -250,6 +268,7 @@ class MediaSessionCallback(var playerNotificationService:PlayerNotificationServi CUSTOM_ACTION_JUMP_BACKWARD -> onRewind() CUSTOM_ACTION_SKIP_FORWARD -> onSkipToNext() CUSTOM_ACTION_SKIP_BACKWARD -> onSkipToPrevious() + CUSTOM_ACTION_CHANGE_SPEED -> onChangeSpeed() } } } diff --git a/android/app/src/main/java/com/audiobookshelf/app/player/PlayerConstants.kt b/android/app/src/main/java/com/audiobookshelf/app/player/PlayerConstants.kt index 0938b912..35d787a7 100644 --- a/android/app/src/main/java/com/audiobookshelf/app/player/PlayerConstants.kt +++ b/android/app/src/main/java/com/audiobookshelf/app/player/PlayerConstants.kt @@ -4,6 +4,7 @@ const val CUSTOM_ACTION_JUMP_FORWARD = "com.audiobookshelf.customAction.jump_for const val CUSTOM_ACTION_JUMP_BACKWARD = "com.audiobookshelf.customAction.jump_backward"; const val CUSTOM_ACTION_SKIP_FORWARD = "com.audiobookshelf.customAction.skip_forward"; const val CUSTOM_ACTION_SKIP_BACKWARD = "com.audiobookshelf.customAction.skip_backward"; +const val CUSTOM_ACTION_CHANGE_SPEED = "com.audiobookshelf.customAction.change_speed"; const val PLAYMETHOD_DIRECTPLAY = 0 const val PLAYMETHOD_DIRECTSTREAM = 1 diff --git a/android/app/src/main/java/com/audiobookshelf/app/player/PlayerNotificationService.kt b/android/app/src/main/java/com/audiobookshelf/app/player/PlayerNotificationService.kt index 15ced0bd..36fe2dd3 100644 --- a/android/app/src/main/java/com/audiobookshelf/app/player/PlayerNotificationService.kt +++ b/android/app/src/main/java/com/audiobookshelf/app/player/PlayerNotificationService.kt @@ -383,6 +383,7 @@ class PlayerNotificationService : MediaBrowserServiceCompat() { val customActionProviders = mutableListOf( JumpBackwardCustomActionProvider(), JumpForwardCustomActionProvider(), + ChangePlaybackSpeedCustomActionProvider() // Will be pushed to far left ) val metadata = playbackSession.getMediaMetadataCompat(ctx) mediaSession.setMetadata(metadata) @@ -1175,5 +1176,37 @@ class PlayerNotificationService : MediaBrowserServiceCompat() { ).build() } } + + inner class ChangePlaybackSpeedCustomActionProvider : CustomActionProvider { + override fun onCustomAction(player: Player, action: String, extras: Bundle?) { + /* + This does not appear to ever get called. Instead, MediaSessionCallback.onCustomAction() is + responsible to reacting to a custom action. + */ + } + + override fun getCustomAction(player: Player): PlaybackStateCompat.CustomAction? { + val playbackRate = mediaManager.getSavedPlaybackRate() + val drawable: Int = when (playbackRate) { + 0.5f -> R.drawable.ic_play_speed_0_5x + 1.0f -> R.drawable.ic_play_speed_1_0x + 1.2f -> R.drawable.ic_play_speed_1_2x + 1.5f -> R.drawable.ic_play_speed_1_5x + 2.0f -> R.drawable.ic_play_speed_2_0x + 3.0f -> R.drawable.ic_play_speed_3_0x + // anything set above 3 will be show the 3x to save from creating 100 icons + else -> R.drawable.ic_play_speed_3_0x + } + val customActionExtras = Bundle() + customActionExtras.putFloat("speed", playbackRate) + return PlaybackStateCompat.CustomAction.Builder( + CUSTOM_ACTION_CHANGE_SPEED, + getContext().getString(R.string.action_skip_backward), + drawable + ) + .setExtras(customActionExtras) + .build() + } + } } diff --git a/android/app/src/main/res/drawable-anydpi/ic_play_speed_0_5x.xml b/android/app/src/main/res/drawable-anydpi/ic_play_speed_0_5x.xml new file mode 100644 index 00000000..dc91ae5c --- /dev/null +++ b/android/app/src/main/res/drawable-anydpi/ic_play_speed_0_5x.xml @@ -0,0 +1,25 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/android/app/src/main/res/drawable-anydpi/ic_play_speed_1_0x.xml b/android/app/src/main/res/drawable-anydpi/ic_play_speed_1_0x.xml new file mode 100644 index 00000000..e0c261c4 --- /dev/null +++ b/android/app/src/main/res/drawable-anydpi/ic_play_speed_1_0x.xml @@ -0,0 +1,27 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/android/app/src/main/res/drawable-anydpi/ic_play_speed_1_2x.xml b/android/app/src/main/res/drawable-anydpi/ic_play_speed_1_2x.xml new file mode 100644 index 00000000..6590aabc --- /dev/null +++ b/android/app/src/main/res/drawable-anydpi/ic_play_speed_1_2x.xml @@ -0,0 +1,27 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/android/app/src/main/res/drawable-anydpi/ic_play_speed_1_5x.xml b/android/app/src/main/res/drawable-anydpi/ic_play_speed_1_5x.xml new file mode 100644 index 00000000..1ed9793a --- /dev/null +++ b/android/app/src/main/res/drawable-anydpi/ic_play_speed_1_5x.xml @@ -0,0 +1,27 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/android/app/src/main/res/drawable-anydpi/ic_play_speed_2_0x.xml b/android/app/src/main/res/drawable-anydpi/ic_play_speed_2_0x.xml new file mode 100644 index 00000000..54ff98ae --- /dev/null +++ b/android/app/src/main/res/drawable-anydpi/ic_play_speed_2_0x.xml @@ -0,0 +1,25 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/android/app/src/main/res/drawable-anydpi/ic_play_speed_3_0x.xml b/android/app/src/main/res/drawable-anydpi/ic_play_speed_3_0x.xml new file mode 100644 index 00000000..6d834aab --- /dev/null +++ b/android/app/src/main/res/drawable-anydpi/ic_play_speed_3_0x.xml @@ -0,0 +1,25 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/android/app/src/main/res/drawable-hdpi/ic_play_speed_0_5x.png b/android/app/src/main/res/drawable-hdpi/ic_play_speed_0_5x.png new file mode 100644 index 0000000000000000000000000000000000000000..ca5a5481e09229467564d22bafd58997c5e0eb25 GIT binary patch literal 500 zcmV)9!hGyuOZZ+i$Nleq-?ttQ%gTM0CxezVii29Lf$ z%>(_x6k&INNQ9$Fg|iGA98W)Si+3t?SeGlwaZ91<6cuEo3!-TNc5f(L!p4Y!hN(8VKbf%2!sPs!riMC>o~NHwQ`VA9lm1ygQgoDnR2U z?i5fB2!f(uG{JkF5(>juE0TfrPb@E94}e4R q)rI)0Zn1CT&e=LTIyyT38-4+ub|nKbn$_6=0000b>!49wOk=$;Q`>Xg0k)}{G?*8=%K9#B7^UApA~$piX<i9LH3~by+iRAMZPf}Ib8RTZAg&vPioJOL&a9RDwtF=ZcOo29FM`AH{s)p(}U!%tt5#3_iPW5?@Mx?N#3N` z3|9qo4=Me<=;^_hU{26c;&-yv?i5Y9`k&Y9B!w%458PpgDSQD*wd{XndttkgH9FAp sOD0^1$9mH%8e$moWX$1kI2`{CzraD#v^>UcuK)l507*qoM6N<$f;cG4t^fc4 literal 0 HcmV?d00001 diff --git a/android/app/src/main/res/drawable-hdpi/ic_play_speed_1_5x.png b/android/app/src/main/res/drawable-hdpi/ic_play_speed_1_5x.png new file mode 100644 index 0000000000000000000000000000000000000000..f5619d8910c232803dda5ff05f6f4ac3e7c1ca13 GIT binary patch literal 465 zcmV;?0WSWDP)#v#n>!C+FQ5V9#+?U<2XF>Z2FOkbUH~ZyCV7^UA_e{q zcj5z}9v~1Pnlg$H0aFE~1C66YrPv(nYx)vA(=Zkgf;D3Lb-Hgv z*xd3ZkW>u;quWH%u~9daUn}2{93P&`H*u<<5=g3t&}+t*cC|Wtes3}%7e`?&VN01VI!P!?bSu01dS82|=g02rVhAPmr*RDH1&%d+FNJ@w%4J-rY~ zmOlNMtX2&T4Gj$qe+LnDEC-P{mM8C11N(37d2jzQkeKDh^5tY;*?9jF*zZ_4kC?wI zY6Ks;NB~qe2H@w8<-zi?6qG_|aQ@D+hCfR$y59RQLJnRFdaZivZ59*@T01=mLSHQ? zfmDOt$&ym&5(4hL&4NNF?*;u{&X2JvbnQwxapZ^oX!RK=R$kpB@hNp!M|p7XH{*j5 z4-|?@h$f)eoh*?B+c!3;vn7N?r)!`j(HXpV(?JhIrp|g*PP$~HxLYTA&X0=X+(D)~ zDuBWcW=X|1M@YL00lD(hQ@pC<_4W2S$eC=khS1*WQN$p&-r6P@GMH(Z@eC+*b6QlR zPi~pf>t*?rXJ_qjz#rl~+XYZu)MUhx@Zoey5~Og$P|QHu1X1cd^*iKcs*u9MzfT#` z7z4{I{8`STvo}n42Sn``0lKp~DdU>hpkurB-RSjGW6xcT9eab%5cIy2I>NnHhl*-4 zEja&vW_hsz6%wnXK+Y!xA;ijVZq3%v(9qEE-|!7SvQ6gr%E);D0000dEyT`vka@uHsg9RY2 zb><^To(yb1lPnPfesldt&_Hl5x-p))2$CCt|I+bc+*wFYtAInmv*$fv1KWVNDX;Y% zQ#%HQH_GprzW^O2dDCE9ulG>9W5C-{_A%)FL^e3$PI6O4p=My?hr|^VlFf>fI$cqa za<=?dBqoFR=HkeZJZ5~hcK#pUh7l#5(s68kPBz{{hA*Yxfe4b1ca&6h*9x+!LQvei zr6pHuo*l=RFd9ZPU6C6J)d*Mpvkd>F{){P}*}gl@CW*l2zgLdGFk1l>YJUXF zy&ocGg%!XOU1I&XnNuEt(JlKQNRPewQr=I}j<8%CB$s?^B6#>^CH^tVlji~F?~|e+ mOfLE(T-8-nR8&;_H~a$YNhb<)!c>p|0000$cnjx51JvX?Y>YhOQJZ?oBi3I78T zS*mR~W6Z;NrL#rk44|Z)+C%MP=RR^WG}fp+omG)DfE6w6&V(cukq0a`s`3pKra|yA zW!o_L;Xd;{#A3CrL^;yXp0QP+PliRPe}HZ9Y~*o>!Otq6rgzA1|zb VkY56Ixvu~K002ovPDHLkV1jFwlGy+N literal 0 HcmV?d00001 diff --git a/android/app/src/main/res/drawable-mdpi/ic_play_speed_1_0x.png b/android/app/src/main/res/drawable-mdpi/ic_play_speed_1_0x.png new file mode 100644 index 0000000000000000000000000000000000000000..d1aa14fe77267e11ed9ad48a5a8850ebda17ea9c GIT binary patch literal 311 zcmV-70m%M|P)x1gLnH9CLuF5k7g1YZmoDyz(V(Xm*bUnx@MC!p&9d>?)65A2BSN-1{$~E zjdj4>YEupT6?p@8&*F`d#mz|p3v)P*EurK1wKe9|q)mW$18z;+fhS$__pwl>aV6mm z_`CS<{F(D_fcOBOP0XT0hh$~)j0=rv!e3MmXTO4I*9#W>22W5mb3Yj=heiMZ002ov JPDHLkV1hu+g@ynC literal 0 HcmV?d00001 diff --git a/android/app/src/main/res/drawable-mdpi/ic_play_speed_1_2x.png b/android/app/src/main/res/drawable-mdpi/ic_play_speed_1_2x.png new file mode 100644 index 0000000000000000000000000000000000000000..39a4c78394b9bef9a71e199e9c58ea493c7e52ab GIT binary patch literal 303 zcmV+~0nq-5P)W3Rhv1B#LbL=F2 zS(cFSKXA_F;K8oPkY-1nzPZ(m0^$~Y*!38wU%;CJPGCP90zmLV9+DvpW9ZK<0dg%GL?F22$ECCkVHY*AUqZr<;t83wax;%rv~&Of002ovPDHLkV1l&= Bg<1dr literal 0 HcmV?d00001 diff --git a/android/app/src/main/res/drawable-mdpi/ic_play_speed_1_5x.png b/android/app/src/main/res/drawable-mdpi/ic_play_speed_1_5x.png new file mode 100644 index 0000000000000000000000000000000000000000..6e387d0b7ba73a03cedc7ebf56578383ee0f29d5 GIT binary patch literal 315 zcmV-B0mS}^P)WqTlViF6?;KL^Kds04%WD&QgJ6P)EXaNR`8PTHL+PtJy zF#~wxJuc4UQ-LjrQR>GaMm;a@RhV~13)tkUtP)7Tq<@fd(y6h1b!^A8 zSw+SFKt$Hy7HoXX!)WI|Rs@7+@Bv=H7kKiT1jKHJPjKlqBR~)|`A`ji0Q=I9@a>q` z*et9wl5c3j(OC3?we_i=2@l5RsP1}h!M%ym&e{x$z=!t}5=ZDS==I$2tUh-768}af zP<&{;AQ(Gepf-!UOMIL01SGaJnVirIiXEsu*W}maOMy<}R!k-*n4DT;v*U}gStRqY z(2$%%6%zNF=ft#CD{!G_E(Cw3mNUPKKCHxTWQ>~68%V5*t$PmL;Clo&vBq3=sUjP6A literal 0 HcmV?d00001 diff --git a/android/app/src/main/res/drawable-mdpi/ic_play_speed_3_0x.png b/android/app/src/main/res/drawable-mdpi/ic_play_speed_3_0x.png new file mode 100644 index 0000000000000000000000000000000000000000..68d4f68de6e944c942a2f969e881849e6eeed609 GIT binary patch literal 346 zcmV-g0j2(lP)ABe~ttif3x%K^<&wUazH2nY}018j9&=NbcI7svb82T=e%O^7w{V6G#Wm=`();T$=v_5(gLt{G%19J<-e0=Cjg{$q{{H33lR!DzW0$ z+3Y*dvOU3^%u2^blX0cpwFv71oa&x!WUN*#TiFd(;^$z@TAl8&Dp)>;#2KU^W4{Mi s&hEadUeKtIkL0=Fbf3Sdpx`(00&LKjzL3I-0{{R307*qoM6N<$f}8x5p8x;= literal 0 HcmV?d00001 diff --git a/android/app/src/main/res/drawable-xhdpi/ic_play_speed_0_5x.png b/android/app/src/main/res/drawable-xhdpi/ic_play_speed_0_5x.png new file mode 100644 index 0000000000000000000000000000000000000000..f062a13b6106820a7a62b5132666bc334593ecdc GIT binary patch literal 664 zcmV;J0%!e+P)t0G?=x%2iT6u;XbmX|Z{ zw>J!80=^cx7I`t>|9HP$6rTt2eV15~9K6F@%tD#)*y+}yd$Zq1C_bO!d+l{U20I{0 z!Y-rEvFeAr<@~-sCOt6C{-@U|sAEN&Td(WEW|#tIyBa|o)Cu6_5uUlwhKrIUFV21o zrZIZ(x*jI(yZp_aX0v$S!h$d_x*0p>7ad2lP6MX-Un*c{kscG5yT^{{!c=-))j01Hm~x{p3}8=&;q*iO`Q$63zv}b{mo|I zhv?$4*ZLjv%D;IX&&N*^j21Aw14I_moA<@hm4#~&D(x*evbYhOeZe%-A6`dLM@c(R zX%6*LOI&IPtxE||RG`(tuiX&*#M zrUp$yr*8uUV8?h6EbJ=g^Up@;dEt(RC*SzDhbs9$+WzNrBAUFtaD8H=-GIfd~KW7hXagb5HZdezphmXI`J zyk&IQUsA-z>L)?{s>@`T30bx+1%#NRZKCElTHPcle$|)Nz~9CK&?XWy+afueFowm1 zlnTe&6Pt|;U^N!XsI>s`KU3P_Pd-;_4%Z(L*K}`ZB;8){6mJr3OjpV!VJ2}-75U?hJ z67Z|>R|)0{K)*{XiQqZf7g=T~KyKhsf0bCapD}ISke|4qx8!F&-tTML+u4jxryJc4p9F^0R0IuRJ>Pfn?Q{P03=q5 zLH!2pf_|IP4;LYY1;~vRQO3#S8rI*j+_T(dbb8fZ=<9RAe(~DdR=3v9=;W<*)-QtU z3-}0jk5&iZuac38^>eh66u%j~!KmLUzNO2#QGEdt?7`{;6-x_}OWFYQDVyxSpFIAg uY?Axq*FZwlw@2Mk=s1q!IF94|e?9>KL8PS+iTE@C0000OK literal 0 HcmV?d00001 diff --git a/android/app/src/main/res/drawable-xhdpi/ic_play_speed_1_2x.png b/android/app/src/main/res/drawable-xhdpi/ic_play_speed_1_2x.png new file mode 100644 index 0000000000000000000000000000000000000000..b01cf919ce57ccc809670793e50ef100a40a66a2 GIT binary patch literal 583 zcmV-N0=WH&P)64~Yl= zBuj0a*#771vRF8d<2a7vIL^O=i0)yweGDPqt!n#KLh!vvPV2i0iZd_8Z@5inaHs&p#I`9ORQ4wV zC~hx^hplygf#U5beKyj78E>yRI5_wq0X1+=E=OGm+ij}P-*12LCQw!kYo7&<9N^i*C6-$^>0E$(`V+N~=2PoJGC?8zS zR06noz5Ll%xdf%p5i$JlzB)BDfdER2$JhPu#0nnMu9}MVVf$u`6+r3i4Jlso!_Hf- ztF4IY+$$jXAA&swyEE-umF$({^_hMJ=!2WQrRcIMjUJQ1L2CqjbH8WRhU-rXsI~Lq zC?t1%IvU&WmVp42NgSpOHq8Bj@G2hCGYW-qAMszuGbu6j z>ts&JhYMz({6{=X{}*YZsRFPSF$N}-Ta*o#MV4Udhb@id`WWTgiT>3hv=X2b=4C&K zwvKEOD-pxnx@fC{fD|}N0OjWjE0HZV#l6l|-bx=sd9{&z$K&bP0-7iQAZdz1%zY~6 zfLW`6{w$IW&qZv37<{yVb086sn7N42Efd255i+C?GqND;zjwLIDhCa^SiJz)TKYC;$q80yYKE(~tkPlqbp0`@FlE zob=5o$y%~3OS1iPa*`xTk|arzB+0)5z*Uj^A}=DhMJ}SVMSdEek43&kyQ3%$ z&x^bl`RoW6J&p605Wkxle_Nl?{va3_eDJ?eD{+IGvA^SS?k4%{M)GV)} zPAR%KS?o6I=0NRz&G1>&6HHZjlHY7)wigq6AMJ_QJc>;>;SQOk!3&D0qUYUw3U__6#UoSJu~9nnT?H|G@At?HxS9LUswJM14dZfq%&#iCwKIzhkLd*b@A{QgnoxyYRz7abQ>zud$n zYH|sT%klL)GK1q@!Ns=T<{vTfmFeV2N S)WF&R0000GRbEX^O)qFw>z5@ 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 literal 0 HcmV?d00001 diff --git a/android/app/src/main/res/drawable-xxhdpi/ic_play_speed_1_5x.png b/android/app/src/main/res/drawable-xxhdpi/ic_play_speed_1_5x.png new file mode 100644 index 0000000000000000000000000000000000000000..99eef85d5d20814249883b33e9283ec289bdb45c GIT binary patch literal 892 zcmV-?1B3jDP)ezb{=D!Sfz0$69 zo?Qt69D@Cg_MqeMNtpjGTdUQ&##ALNeidxkE0{3}UJE&_2aS^r*r))3=o+{v2i6yEy1WMkhK|2PT&Is4gI zPl2>R->T0VFlK%}-w^Cl{cvp<7XHUW{M*A5lAnFV=Neh=2&{7{QWCpcGocG zd{mzV#twcE|AYFOU>|{arB8yBb=98H@D&4P<7sB5;n)6^3i}9@7w9!stt}WcgGVL* z%tTm>btfOESc@6@3OWlEH=!G+abu}jOvO`uXl7|hpzN=hedz+y+Q&5)Gjjs^3&i83 zo2DSeQ{?|79@=beNuWH!$N5p#ooYs!pua$TiYY_*1kE~;gL1<+>XU;}49=Z@hMAQA zy{)xCXMwhW@>nj$1v^udu?7|y+XNS16UbgjT+cdgfwCYcTUU2-&RC$yW_8>GW#-ZD z)Nd7MERZ&Wi&=oOcF_hbblFuXN;cWq3Z&x-%IiM2#hY-J-U7vq{)p==-e3ud>q*CR zV80#J=Cpb32@*&Tf$}!Lwb=67&adg&xT3GV=i)VE&oe z1`p-CuGc-FJa(^Lb~BvNn*6;$*`HK{MESma_T`!}H#25ZkWm)y_Ajag8wCo!UxNM0 z?4$Z5j36n}L40^5G;?TX#zp@@d=G}7)nylcvtYYGn!caaClQ0xqM3~KV=(?tyVJQ6 zSn&!2Wu^_NWbp*-AP~WL)99vZR@<=FwdyTLP}Zv7>SKcK0)3YSKdFxeMqxGOC!590 zu_7E3f6lddJHgK4>X+b?!cF1rl`D4$$w?UJ96#FgaD zOa^ZTlSBYP5&}VJb9Lo7j^j9v<2a7vIF92uj^j9v^IrkrMxR}he?5OeSj=^hhafLO zx*&ftc@44)a;xLkB>4ZD=F$1zgZ!di$6SOk@g^-IU(}F11-VuqO9?(7jk#31Aa^?U zLqQCVE@Vnx)z1cm@x6~yY{{244UhE##-yD+v+%M4 zo-goOY&z=H*XkG(52kNg>~FDOTtpFHNFE&1XL!DqxgXSKb<%?R37=K3aPhLpd$lJsy_$u zHJQN#W!4{$8BT4=QSxy^AjTmdH=4zhpv<790Wvo`2ItJi6hTGZDvVwvh(YaSOw&m+ zLA>>HXHA>3!2Bbg2cc*;5I6kR>KFATHG4k?42QF802ki6ksNL!A7|(_@N9sqmH@Gp z$n6%L20FZ3(;?~rH3b_J0U~M>WG8wJ;vPm2n=u?VZ)gZefK)#O8UjR6)+)*-J?Amh z&#HrN7Kp*=#Nnd;hj3B3Pt_06Iu5a3GBbMHEt$X;i296Dmh}e)SSLVx)ms>Q>eOZu#7OVN5oKPa8<2T6s<%LSWF}h;`Ze}MX)n%* z!Qs!UxdWzU(_5Vj%FmXjEJb;Jk8Tq7?_Ie}?+*+P65`v#kLB8~&`W)05{AAczWniOrB-wUiThtm1blJI$m9Dd- zaGD6ue~R5_wdp~fMHB{uN8vl-e={|Az^DUmW+m$Vml{4OvFh38{_}rHJGXvQ}eX`5P#lnwR z$QFytc_2Lh7UV}SsviPz!5hTm#b7XC{FDDS=juTWP`)-ZkP7YQjB^~vaU92S9LI4S h$8j9TaUACY`~@iwpV3W^kR|{C002ovPDHLkV1mX-*M9&2 literal 0 HcmV?d00001 diff --git a/android/app/src/main/res/drawable-xxhdpi/ic_play_speed_3_0x.png b/android/app/src/main/res/drawable-xxhdpi/ic_play_speed_3_0x.png new file mode 100644 index 0000000000000000000000000000000000000000..e35c0ccfefa5335a75fff3d97dd172e4aac10bc0 GIT binary patch literal 1049 zcmV+!1m^pRP)zH?e3lD*%LDgh-GU@m&aP!_){Hf6YrH(y0@KvM1 z^kmXG-h!L&y5gfJ7dpquK>6l=Ik`kAX9iQTnWO%#g!$9CcTTRHoWoc4~dq7v-KXQRW=DjH;-?0GXHeSwva($Y)>ly0 zJ5-;7@?g=*dC@U%0%i7B-x;tzK|@4Am)4CTvd-o8=Z<@O)8mFMm23d)RLsZ9#X?e(TFdiX7n&ez;# zC~v^cimSxQt$av6$BKSRhW`a+fHNBVl9L2|wB4cc#@+J|PCiFbeG1BZ%3Z~V$$1vL zN$p6moy9g=6B@Vd)aC%nGT#RYUy9$t&xeZ5q;o}_?uXcZRGSFOmqcMQqW?QAxB;U~ zc(p1~Zr5f-()eDCk4e>O1Pc;G;Db%yXiwNTTa~LXyS?bMYBJj`27U`23zES#8)F0u z6GS*~m|uOS!roEc3SoR|%e7P!8KFMdrQ_P`3j_ET*t{7Gg;{2~Pio)7*?x8MJ&NjE z1pj Date: Mon, 6 Feb 2023 16:34:12 -0600 Subject: [PATCH 2/5] Update Android Auto playback speed string --- .../com/audiobookshelf/app/player/PlayerNotificationService.kt | 2 +- android/app/src/main/res/values/strings.xml | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/android/app/src/main/java/com/audiobookshelf/app/player/PlayerNotificationService.kt b/android/app/src/main/java/com/audiobookshelf/app/player/PlayerNotificationService.kt index 36fe2dd3..910e71e8 100644 --- a/android/app/src/main/java/com/audiobookshelf/app/player/PlayerNotificationService.kt +++ b/android/app/src/main/java/com/audiobookshelf/app/player/PlayerNotificationService.kt @@ -1201,7 +1201,7 @@ class PlayerNotificationService : MediaBrowserServiceCompat() { customActionExtras.putFloat("speed", playbackRate) return PlaybackStateCompat.CustomAction.Builder( CUSTOM_ACTION_CHANGE_SPEED, - getContext().getString(R.string.action_skip_backward), + getContext().getString(R.string.action_change_speed), drawable ) .setExtras(customActionExtras) diff --git a/android/app/src/main/res/values/strings.xml b/android/app/src/main/res/values/strings.xml index c47bb9d8..5048dff1 100644 --- a/android/app/src/main/res/values/strings.xml +++ b/android/app/src/main/res/values/strings.xml @@ -10,4 +10,5 @@ Jump Backward Skip Forward Skip Backward + Change Playback Speed From 405cd21c32a4ea9c70b03dd2628fdd59f376506d Mon Sep 17 00:00:00 2001 From: advplyr Date: Mon, 6 Feb 2023 17:20:39 -0600 Subject: [PATCH 3/5] Emit playback speed change event when Android Auto changes speed --- .../java/com/audiobookshelf/app/media/MediaManager.kt | 11 ++++++----- .../audiobookshelf/app/player/MediaSessionCallback.kt | 7 ++++--- .../app/player/PlayerNotificationService.kt | 3 +++ .../com/audiobookshelf/app/plugins/AbsAudioPlayer.kt | 4 ++++ components/app/AudioPlayer.vue | 8 ++++++++ 5 files changed, 25 insertions(+), 8 deletions(-) diff --git a/android/app/src/main/java/com/audiobookshelf/app/media/MediaManager.kt b/android/app/src/main/java/com/audiobookshelf/app/media/MediaManager.kt index c5715ebe..a986a4e6 100644 --- a/android/app/src/main/java/com/audiobookshelf/app/media/MediaManager.kt +++ b/android/app/src/main/java/com/audiobookshelf/app/media/MediaManager.kt @@ -70,21 +70,22 @@ class MediaManager(private var apiHandler: ApiHandler, var ctx: Context) { if (userSettingsPref != null) { try { val userSettings = JSObject(userSettingsPref) - userSettings.put("playbackRate", newRate.toDouble()) + userSettings.put("playbackRate", newRate.toDouble()) // TODO: Handle storing exact values? This actually comes out to something like 2.00000004432 sharedPrefEditor.putString("userSettings", userSettings.toString()) - sharedPrefEditor.commit() + sharedPrefEditor.apply() userSettingsPlaybackRate = newRate - Log.d(tag, "Saved userSettings JSON from Android Auto") + Log.d(tag, "Saved userSettings JSON from Android Auto with playbackRate=$newRate") } catch(je:JSONException) { Log.e(tag, "Failed to save userSettings JSON ${je.localizedMessage}") } } else { // Not sure if this is the best place for this, but if a user has not changed any user settings in the app // the object will not exist yet, could be moved to a centralized place or created on first app load - var userSettings = JSONObject() + val userSettings = JSONObject() userSettings.put("playbackRate", newRate.toDouble()) sharedPrefEditor.putString("userSettings", userSettings.toString()) - Log.d(tag, "Created and saved userSettings JSON from Android Auto") + userSettingsPlaybackRate = newRate + Log.d(tag, "Created and saved userSettings JSON from Android Auto with playbackRate=$newRate") } } } diff --git a/android/app/src/main/java/com/audiobookshelf/app/player/MediaSessionCallback.kt b/android/app/src/main/java/com/audiobookshelf/app/player/MediaSessionCallback.kt index bdddcae8..6406288c 100644 --- a/android/app/src/main/java/com/audiobookshelf/app/player/MediaSessionCallback.kt +++ b/android/app/src/main/java/com/audiobookshelf/app/player/MediaSessionCallback.kt @@ -90,9 +90,9 @@ class MediaSessionCallback(var playerNotificationService:PlayerNotificationServi private fun onChangeSpeed() { // cycle to next speed, only contains preset android app options, as each increment needs it's own icon - var mediaManager = playerNotificationService.mediaManager - var currentSpeed = mediaManager.getSavedPlaybackRate() - var newSpeed = when (currentSpeed) { + val mediaManager = playerNotificationService.mediaManager + val currentSpeed = mediaManager.getSavedPlaybackRate() + val newSpeed = when (currentSpeed) { 0.5f -> 1.0f 1.0f -> 1.2f 1.2f -> 1.5f @@ -104,6 +104,7 @@ class MediaSessionCallback(var playerNotificationService:PlayerNotificationServi } mediaManager.setSavedPlaybackRate(newSpeed) playerNotificationService.setPlaybackSpeed(newSpeed) + playerNotificationService.clientEventEmitter?.onPlaybackSpeedChanged(newSpeed) } override fun onPlayFromMediaId(mediaId: String?, extras: Bundle?) { diff --git a/android/app/src/main/java/com/audiobookshelf/app/player/PlayerNotificationService.kt b/android/app/src/main/java/com/audiobookshelf/app/player/PlayerNotificationService.kt index 910e71e8..8e678e4a 100644 --- a/android/app/src/main/java/com/audiobookshelf/app/player/PlayerNotificationService.kt +++ b/android/app/src/main/java/com/audiobookshelf/app/player/PlayerNotificationService.kt @@ -80,6 +80,7 @@ class PlayerNotificationService : MediaBrowserServiceCompat() { fun onProgressSyncSuccess() fun onNetworkMeteredChanged(isUnmetered:Boolean) fun onMediaItemHistoryUpdated(mediaItemHistory:MediaItemHistory) + fun onPlaybackSpeedChanged(playbackSpeed:Float) } private val binder = LocalBinder() @@ -1187,6 +1188,8 @@ class PlayerNotificationService : MediaBrowserServiceCompat() { override fun getCustomAction(player: Player): PlaybackStateCompat.CustomAction? { val playbackRate = mediaManager.getSavedPlaybackRate() + + // TODO: Handle custom playback rates like 0.7, 1.3, 1.4, etc val drawable: Int = when (playbackRate) { 0.5f -> R.drawable.ic_play_speed_0_5x 1.0f -> R.drawable.ic_play_speed_1_0x diff --git a/android/app/src/main/java/com/audiobookshelf/app/plugins/AbsAudioPlayer.kt b/android/app/src/main/java/com/audiobookshelf/app/plugins/AbsAudioPlayer.kt index ededb470..3272598f 100644 --- a/android/app/src/main/java/com/audiobookshelf/app/plugins/AbsAudioPlayer.kt +++ b/android/app/src/main/java/com/audiobookshelf/app/plugins/AbsAudioPlayer.kt @@ -97,6 +97,10 @@ class AbsAudioPlayer : Plugin() { override fun onMediaItemHistoryUpdated(mediaItemHistory:MediaItemHistory) { notifyListeners("onMediaItemHistoryUpdated", JSObject(jacksonMapper.writeValueAsString(mediaItemHistory))) } + + override fun onPlaybackSpeedChanged(playbackSpeed:Float) { + emit("onPlaybackSpeedChanged", playbackSpeed) + } }) MediaEventManager.clientEventEmitter = playerNotificationService.clientEventEmitter diff --git a/components/app/AudioPlayer.vue b/components/app/AudioPlayer.vue index 00f82f90..76a1f1a4 100644 --- a/components/app/AudioPlayer.vue +++ b/components/app/AudioPlayer.vue @@ -143,6 +143,7 @@ export default { onMetadataListener: null, onProgressSyncFailing: null, onProgressSyncSuccess: null, + onPlaybackSpeedChangedListener: null, touchStartY: 0, touchStartTime: 0, touchEndY: 0, @@ -814,6 +815,11 @@ export default { this.$toast.error(`Playback Failed: ${errorMessage}`) this.endPlayback() }, + onPlaybackSpeedChanged(data) { + if (!data.value || isNaN(data.value)) return + this.currentPlaybackRate = Number(data.value) + this.updateTimestamp() + }, async init() { this.useChapterTrack = await this.$localStore.getUseChapterTrack() this.lockUi = await this.$localStore.getPlayerLock() @@ -825,6 +831,7 @@ export default { this.onMetadataListener = AbsAudioPlayer.addListener('onMetadata', this.onMetadata) this.onProgressSyncFailing = AbsAudioPlayer.addListener('onProgressSyncFailing', this.showProgressSyncIsFailing) this.onProgressSyncSuccess = AbsAudioPlayer.addListener('onProgressSyncSuccess', this.showProgressSyncSuccess) + this.onPlaybackSpeedChangedListener = AbsAudioPlayer.addListener('onPlaybackSpeedChanged', this.onPlaybackSpeedChanged) }, screenOrientationChange() { setTimeout(() => { @@ -900,6 +907,7 @@ export default { if (this.onPlaybackFailedListener) this.onPlaybackFailedListener.remove() if (this.onProgressSyncFailing) this.onProgressSyncFailing.remove() if (this.onProgressSyncSuccess) this.onProgressSyncSuccess.remove() + if (this.onPlaybackSpeedChangedListener) this.onPlaybackSpeedChangedListener.remove() clearInterval(this.playInterval) } } From b2b647e2f7f4623415966d52ca02f891d02ae1bf Mon Sep 17 00:00:00 2001 From: Alex Date: Mon, 6 Feb 2023 20:41:35 -0600 Subject: [PATCH 4/5] fix float conversion, rounding for icons --- .../com/audiobookshelf/app/media/MediaManager.kt | 5 +++-- .../app/player/MediaSessionCallback.kt | 16 ++++++++-------- .../app/player/PlayerNotificationService.kt | 14 +++++++------- 3 files changed, 18 insertions(+), 17 deletions(-) diff --git a/android/app/src/main/java/com/audiobookshelf/app/media/MediaManager.kt b/android/app/src/main/java/com/audiobookshelf/app/media/MediaManager.kt index a986a4e6..26829743 100644 --- a/android/app/src/main/java/com/audiobookshelf/app/media/MediaManager.kt +++ b/android/app/src/main/java/com/audiobookshelf/app/media/MediaManager.kt @@ -70,7 +70,8 @@ class MediaManager(private var apiHandler: ApiHandler, var ctx: Context) { if (userSettingsPref != null) { try { val userSettings = JSObject(userSettingsPref) - userSettings.put("playbackRate", newRate.toDouble()) // TODO: Handle storing exact values? This actually comes out to something like 2.00000004432 + // toString().toDouble() to prevent float conversion issues (ex 1.2f becomes 1.2000000476837158d) + userSettings.put("playbackRate", newRate.toString().toDouble()) sharedPrefEditor.putString("userSettings", userSettings.toString()) sharedPrefEditor.apply() userSettingsPlaybackRate = newRate @@ -82,7 +83,7 @@ class MediaManager(private var apiHandler: ApiHandler, var ctx: Context) { // Not sure if this is the best place for this, but if a user has not changed any user settings in the app // the object will not exist yet, could be moved to a centralized place or created on first app load val userSettings = JSONObject() - userSettings.put("playbackRate", newRate.toDouble()) + userSettings.put("playbackRate", newRate.toString().toDouble()) sharedPrefEditor.putString("userSettings", userSettings.toString()) userSettingsPlaybackRate = newRate Log.d(tag, "Created and saved userSettings JSON from Android Auto with playbackRate=$newRate") diff --git a/android/app/src/main/java/com/audiobookshelf/app/player/MediaSessionCallback.kt b/android/app/src/main/java/com/audiobookshelf/app/player/MediaSessionCallback.kt index 6406288c..12e20357 100644 --- a/android/app/src/main/java/com/audiobookshelf/app/player/MediaSessionCallback.kt +++ b/android/app/src/main/java/com/audiobookshelf/app/player/MediaSessionCallback.kt @@ -90,15 +90,15 @@ class MediaSessionCallback(var playerNotificationService:PlayerNotificationServi private fun onChangeSpeed() { // cycle to next speed, only contains preset android app options, as each increment needs it's own icon + // Rounding values in the event a non preset value (.5, 1, 1.2, 1.5, 2, 3) is selected in the phone app val mediaManager = playerNotificationService.mediaManager - val currentSpeed = mediaManager.getSavedPlaybackRate() - val newSpeed = when (currentSpeed) { - 0.5f -> 1.0f - 1.0f -> 1.2f - 1.2f -> 1.5f - 1.5f -> 2.0f - 2.0f -> 3.0f - 3.0f -> 0.5f + val newSpeed = when (mediaManager.getSavedPlaybackRate()) { + in 0.5f..0.7f -> 1.0f + in 0.8f..1.0f -> 1.2f + in 1.1f..1.2f -> 1.5f + in 1.3f..1.5f -> 2.0f + in 1.6f..2.0f -> 3.0f + in 2.1f..3.0f -> 0.5f // anything set above 3 (can happen in the android app) will be reset to 1 else -> 1.0f } diff --git a/android/app/src/main/java/com/audiobookshelf/app/player/PlayerNotificationService.kt b/android/app/src/main/java/com/audiobookshelf/app/player/PlayerNotificationService.kt index 8e678e4a..c2dca783 100644 --- a/android/app/src/main/java/com/audiobookshelf/app/player/PlayerNotificationService.kt +++ b/android/app/src/main/java/com/audiobookshelf/app/player/PlayerNotificationService.kt @@ -1189,14 +1189,14 @@ class PlayerNotificationService : MediaBrowserServiceCompat() { override fun getCustomAction(player: Player): PlaybackStateCompat.CustomAction? { val playbackRate = mediaManager.getSavedPlaybackRate() - // TODO: Handle custom playback rates like 0.7, 1.3, 1.4, etc + // Rounding values in the event a non preset value (.5, 1, 1.2, 1.5, 2, 3) is selected in the phone app val drawable: Int = when (playbackRate) { - 0.5f -> R.drawable.ic_play_speed_0_5x - 1.0f -> R.drawable.ic_play_speed_1_0x - 1.2f -> R.drawable.ic_play_speed_1_2x - 1.5f -> R.drawable.ic_play_speed_1_5x - 2.0f -> R.drawable.ic_play_speed_2_0x - 3.0f -> R.drawable.ic_play_speed_3_0x + in 0.5f..0.7f -> R.drawable.ic_play_speed_0_5x + in 0.8f..1.0f -> R.drawable.ic_play_speed_1_0x + in 1.1f..1.2f -> R.drawable.ic_play_speed_1_2x + in 1.3f..1.5f -> R.drawable.ic_play_speed_1_5x + in 1.6f..2.0f -> R.drawable.ic_play_speed_2_0x + in 2.1f..3.0f -> R.drawable.ic_play_speed_3_0x // anything set above 3 will be show the 3x to save from creating 100 icons else -> R.drawable.ic_play_speed_3_0x } From 86c5d1a3e9e77a85fdcc1ba68e693f92d999999c Mon Sep 17 00:00:00 2001 From: advplyr Date: Tue, 7 Feb 2023 16:10:02 -0600 Subject: [PATCH 5/5] Refresh media session connector actions when changing speed on app. Update rounding values for Android Auto speed icons --- .../audiobookshelf/app/media/MediaManager.kt | 2 +- .../app/player/PlayerNotificationService.kt | 45 ++++++++++++------- 2 files changed, 30 insertions(+), 17 deletions(-) diff --git a/android/app/src/main/java/com/audiobookshelf/app/media/MediaManager.kt b/android/app/src/main/java/com/audiobookshelf/app/media/MediaManager.kt index 26829743..e5f335a2 100644 --- a/android/app/src/main/java/com/audiobookshelf/app/media/MediaManager.kt +++ b/android/app/src/main/java/com/audiobookshelf/app/media/MediaManager.kt @@ -33,7 +33,7 @@ class MediaManager(private var apiHandler: ApiHandler, var ctx: Context) { var serverItemsInProgress = listOf() var serverLibraries = listOf() - private var userSettingsPlaybackRate:Float? = null + var userSettingsPlaybackRate:Float? = null fun getIsLibrary(id:String) : Boolean { return serverLibraries.find { it.id == id } != null diff --git a/android/app/src/main/java/com/audiobookshelf/app/player/PlayerNotificationService.kt b/android/app/src/main/java/com/audiobookshelf/app/player/PlayerNotificationService.kt index c2dca783..079a1c56 100644 --- a/android/app/src/main/java/com/audiobookshelf/app/player/PlayerNotificationService.kt +++ b/android/app/src/main/java/com/audiobookshelf/app/player/PlayerNotificationService.kt @@ -381,24 +381,15 @@ class PlayerNotificationService : MediaBrowserServiceCompat() { } isClosed = false - val customActionProviders = mutableListOf( - JumpBackwardCustomActionProvider(), - JumpForwardCustomActionProvider(), - ChangePlaybackSpeedCustomActionProvider() // Will be pushed to far left - ) + val metadata = playbackSession.getMediaMetadataCompat(ctx) mediaSession.setMetadata(metadata) val mediaItems = playbackSession.getMediaItems() val playbackRateToUse = playbackRate ?: initialPlaybackRate ?: 1f initialPlaybackRate = playbackRate - if (playbackSession.mediaPlayer != PLAYER_CAST && mediaItems.size > 1) { - customActionProviders.addAll(listOf( - SkipBackwardCustomActionProvider(), - SkipForwardCustomActionProvider(), - )) - } - mediaSessionConnector.setCustomActionProviders(*customActionProviders.toTypedArray()) + // Set actions on Android Auto like jump forward/backward + setMediaSessionConnectorCustomActions(playbackSession) playbackSession.mediaPlayer = getMediaPlayer() @@ -476,6 +467,22 @@ class PlayerNotificationService : MediaBrowserServiceCompat() { } } + private fun setMediaSessionConnectorCustomActions(playbackSession:PlaybackSession) { + val mediaItems = playbackSession.getMediaItems() + val customActionProviders = mutableListOf( + JumpBackwardCustomActionProvider(), + JumpForwardCustomActionProvider(), + ChangePlaybackSpeedCustomActionProvider() // Will be pushed to far left + ) + if (playbackSession.mediaPlayer != PLAYER_CAST && mediaItems.size > 1) { + customActionProviders.addAll(listOf( + SkipBackwardCustomActionProvider(), + SkipForwardCustomActionProvider(), + )) + } + mediaSessionConnector.setCustomActionProviders(*customActionProviders.toTypedArray()) + } + fun handlePlayerPlaybackError(errorMessage:String) { // On error and was attempting to direct play - fallback to transcode currentPlaybackSession?.let { playbackSession -> @@ -828,7 +835,13 @@ class PlayerNotificationService : MediaBrowserServiceCompat() { } fun setPlaybackSpeed(speed: Float) { + mediaManager.userSettingsPlaybackRate = speed currentPlayer.setPlaybackSpeed(speed) + + // Refresh Android Auto actions + mediaProgressSyncer.currentPlaybackSession?.let { + setMediaSessionConnectorCustomActions(it) + } } fun closePlayback(calledOnError:Boolean? = false) { @@ -1193,10 +1206,10 @@ class PlayerNotificationService : MediaBrowserServiceCompat() { val drawable: Int = when (playbackRate) { in 0.5f..0.7f -> R.drawable.ic_play_speed_0_5x in 0.8f..1.0f -> R.drawable.ic_play_speed_1_0x - in 1.1f..1.2f -> R.drawable.ic_play_speed_1_2x - in 1.3f..1.5f -> R.drawable.ic_play_speed_1_5x - in 1.6f..2.0f -> R.drawable.ic_play_speed_2_0x - in 2.1f..3.0f -> R.drawable.ic_play_speed_3_0x + in 1.1f..1.3f -> R.drawable.ic_play_speed_1_2x + in 1.4f..1.7f -> R.drawable.ic_play_speed_1_5x + in 1.8f..2.4f -> R.drawable.ic_play_speed_2_0x + in 2.5f..3.0f -> R.drawable.ic_play_speed_3_0x // anything set above 3 will be show the 3x to save from creating 100 icons else -> R.drawable.ic_play_speed_3_0x }