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..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 @@ -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 @@ -32,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 @@ -61,6 +62,35 @@ 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) + // 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 + 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 + val userSettings = JSONObject() + 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") + } + } + } + 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..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 @@ -88,6 +88,25 @@ 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 + // 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 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 + } + mediaManager.setSavedPlaybackRate(newSpeed) + playerNotificationService.setPlaybackSpeed(newSpeed) + playerNotificationService.clientEventEmitter?.onPlaybackSpeedChanged(newSpeed) + } + override fun onPlayFromMediaId(mediaId: String?, extras: Bundle?) { Log.d(tag, "ON PLAY FROM MEDIA ID $mediaId") val libraryItemWrapper: LibraryItemWrapper? @@ -250,6 +269,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..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 @@ -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() @@ -380,23 +381,15 @@ class PlayerNotificationService : MediaBrowserServiceCompat() { } isClosed = false - val customActionProviders = mutableListOf( - JumpBackwardCustomActionProvider(), - JumpForwardCustomActionProvider(), - ) + 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() @@ -474,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 -> @@ -826,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) { @@ -1175,5 +1190,39 @@ 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() + + // 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) { + 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.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 + } + val customActionExtras = Bundle() + customActionExtras.putFloat("speed", playbackRate) + return PlaybackStateCompat.CustomAction.Builder( + CUSTOM_ACTION_CHANGE_SPEED, + getContext().getString(R.string.action_change_speed), + drawable + ) + .setExtras(customActionExtras) + .build() + } + } } 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/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 00000000..ca5a5481 Binary files /dev/null and b/android/app/src/main/res/drawable-hdpi/ic_play_speed_0_5x.png differ diff --git a/android/app/src/main/res/drawable-hdpi/ic_play_speed_1_0x.png b/android/app/src/main/res/drawable-hdpi/ic_play_speed_1_0x.png new file mode 100644 index 00000000..cf490b4d Binary files /dev/null and b/android/app/src/main/res/drawable-hdpi/ic_play_speed_1_0x.png differ diff --git a/android/app/src/main/res/drawable-hdpi/ic_play_speed_1_2x.png b/android/app/src/main/res/drawable-hdpi/ic_play_speed_1_2x.png new file mode 100644 index 00000000..9c8aefb2 Binary files /dev/null and b/android/app/src/main/res/drawable-hdpi/ic_play_speed_1_2x.png differ 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 00000000..f5619d89 Binary files /dev/null and b/android/app/src/main/res/drawable-hdpi/ic_play_speed_1_5x.png differ diff --git a/android/app/src/main/res/drawable-hdpi/ic_play_speed_2_0x.png b/android/app/src/main/res/drawable-hdpi/ic_play_speed_2_0x.png new file mode 100644 index 00000000..2be3d42f Binary files /dev/null and b/android/app/src/main/res/drawable-hdpi/ic_play_speed_2_0x.png differ diff --git a/android/app/src/main/res/drawable-hdpi/ic_play_speed_3_0x.png b/android/app/src/main/res/drawable-hdpi/ic_play_speed_3_0x.png new file mode 100644 index 00000000..4c1928de Binary files /dev/null and b/android/app/src/main/res/drawable-hdpi/ic_play_speed_3_0x.png differ diff --git a/android/app/src/main/res/drawable-mdpi/ic_play_speed_0_5x.png b/android/app/src/main/res/drawable-mdpi/ic_play_speed_0_5x.png new file mode 100644 index 00000000..536f9d93 Binary files /dev/null and b/android/app/src/main/res/drawable-mdpi/ic_play_speed_0_5x.png differ 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 00000000..d1aa14fe Binary files /dev/null and b/android/app/src/main/res/drawable-mdpi/ic_play_speed_1_0x.png differ 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 00000000..39a4c783 Binary files /dev/null and b/android/app/src/main/res/drawable-mdpi/ic_play_speed_1_2x.png differ 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 00000000..6e387d0b Binary files /dev/null and b/android/app/src/main/res/drawable-mdpi/ic_play_speed_1_5x.png differ diff --git a/android/app/src/main/res/drawable-mdpi/ic_play_speed_2_0x.png b/android/app/src/main/res/drawable-mdpi/ic_play_speed_2_0x.png new file mode 100644 index 00000000..f1385c28 Binary files /dev/null and b/android/app/src/main/res/drawable-mdpi/ic_play_speed_2_0x.png differ 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 00000000..68d4f68d Binary files /dev/null and b/android/app/src/main/res/drawable-mdpi/ic_play_speed_3_0x.png differ 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 00000000..f062a13b Binary files /dev/null and b/android/app/src/main/res/drawable-xhdpi/ic_play_speed_0_5x.png differ diff --git a/android/app/src/main/res/drawable-xhdpi/ic_play_speed_1_0x.png b/android/app/src/main/res/drawable-xhdpi/ic_play_speed_1_0x.png new file mode 100644 index 00000000..025ad26a Binary files /dev/null and b/android/app/src/main/res/drawable-xhdpi/ic_play_speed_1_0x.png differ 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 00000000..b01cf919 Binary files /dev/null and b/android/app/src/main/res/drawable-xhdpi/ic_play_speed_1_2x.png differ diff --git a/android/app/src/main/res/drawable-xhdpi/ic_play_speed_1_5x.png b/android/app/src/main/res/drawable-xhdpi/ic_play_speed_1_5x.png new file mode 100644 index 00000000..fab44afd Binary files /dev/null and b/android/app/src/main/res/drawable-xhdpi/ic_play_speed_1_5x.png differ diff --git a/android/app/src/main/res/drawable-xhdpi/ic_play_speed_2_0x.png b/android/app/src/main/res/drawable-xhdpi/ic_play_speed_2_0x.png new file mode 100644 index 00000000..2eac485d Binary files /dev/null and b/android/app/src/main/res/drawable-xhdpi/ic_play_speed_2_0x.png differ diff --git a/android/app/src/main/res/drawable-xhdpi/ic_play_speed_3_0x.png b/android/app/src/main/res/drawable-xhdpi/ic_play_speed_3_0x.png new file mode 100644 index 00000000..fb7b3d9f Binary files /dev/null and b/android/app/src/main/res/drawable-xhdpi/ic_play_speed_3_0x.png differ diff --git a/android/app/src/main/res/drawable-xxhdpi/ic_play_speed_0_5x.png b/android/app/src/main/res/drawable-xxhdpi/ic_play_speed_0_5x.png new file mode 100644 index 00000000..1aa6aa9d Binary files /dev/null and b/android/app/src/main/res/drawable-xxhdpi/ic_play_speed_0_5x.png differ diff --git a/android/app/src/main/res/drawable-xxhdpi/ic_play_speed_1_0x.png b/android/app/src/main/res/drawable-xxhdpi/ic_play_speed_1_0x.png new file mode 100644 index 00000000..f39d6661 Binary files /dev/null and b/android/app/src/main/res/drawable-xxhdpi/ic_play_speed_1_0x.png differ diff --git a/android/app/src/main/res/drawable-xxhdpi/ic_play_speed_1_2x.png b/android/app/src/main/res/drawable-xxhdpi/ic_play_speed_1_2x.png new file mode 100644 index 00000000..78534fd7 Binary files /dev/null and b/android/app/src/main/res/drawable-xxhdpi/ic_play_speed_1_2x.png differ 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 00000000..99eef85d Binary files /dev/null and b/android/app/src/main/res/drawable-xxhdpi/ic_play_speed_1_5x.png differ diff --git a/android/app/src/main/res/drawable-xxhdpi/ic_play_speed_2_0x.png b/android/app/src/main/res/drawable-xxhdpi/ic_play_speed_2_0x.png new file mode 100644 index 00000000..30382a61 Binary files /dev/null and b/android/app/src/main/res/drawable-xxhdpi/ic_play_speed_2_0x.png differ 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 00000000..e35c0ccf Binary files /dev/null and b/android/app/src/main/res/drawable-xxhdpi/ic_play_speed_3_0x.png differ 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 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) } }