Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Unverified Commit ccdd16b5 authored by Luca Stefani's avatar Luca Stefani Committed by Sebastiano Barezzi
Browse files

Twelve: Add notification shuffle/repeat

Change-Id: I12c2bf5774f22fb12f3a57c77c6a011436390cdb
parent eb138600
Loading
Loading
Loading
Loading
+100 −6
Original line number Diff line number Diff line
@@ -7,6 +7,7 @@ package org.lineageos.twelve.services

import android.app.PendingIntent
import android.content.Intent
import android.content.res.Resources
import android.media.audiofx.AudioEffect
import android.os.Bundle
import android.os.IBinder
@@ -25,6 +26,7 @@ import androidx.media3.common.util.UnstableApi
import androidx.media3.common.util.Util
import androidx.media3.exoplayer.DefaultLoadControl
import androidx.media3.exoplayer.ExoPlayer
import androidx.media3.session.CommandButton
import androidx.media3.session.DefaultMediaNotificationProvider
import androidx.media3.session.LibraryResult
import androidx.media3.session.MediaController
@@ -42,21 +44,24 @@ import org.lineageos.twelve.R
import org.lineageos.twelve.TwelveApplication
import org.lineageos.twelve.ext.enableFloatOutput
import org.lineageos.twelve.ext.enableOffload
import org.lineageos.twelve.ext.next
import org.lineageos.twelve.ext.setOffloadEnabled
import org.lineageos.twelve.ext.skipSilence
import org.lineageos.twelve.ext.stopPlaybackOnTaskRemoved
import org.lineageos.twelve.ext.typedRepeatMode
import org.lineageos.twelve.models.RepeatMode
import org.lineageos.twelve.ui.widgets.NowPlayingAppWidgetProvider

@OptIn(UnstableApi::class)
class PlaybackService : MediaLibraryService(), LifecycleOwner {
    enum class CustomCommand(val value: String, extras: Bundle) {
    enum class CustomCommand {
        /**
         * Toggles audio offload mode.
         *
         * Arguments:
         * - [ARG_VALUE] ([Boolean]): Whether to enable or disable offload
         */
        TOGGLE_OFFLOAD("toggle_offload", Bundle.EMPTY),
        TOGGLE_OFFLOAD,

        /**
         * Toggles skip silence.
@@ -64,7 +69,7 @@ class PlaybackService : MediaLibraryService(), LifecycleOwner {
         * Arguments:
         * - [ARG_VALUE] ([Boolean]): Whether to enable or disable skip silence
         */
        TOGGLE_SKIP_SILENCE("toggle_skip_silence", Bundle.EMPTY),
        TOGGLE_SKIP_SILENCE,

        /**
         * Get the audio session ID.
@@ -72,9 +77,68 @@ class PlaybackService : MediaLibraryService(), LifecycleOwner {
         * Response:
         * - [RSP_VALUE] ([Int]): The audio session ID
         */
        GET_AUDIO_SESSION_ID("get_audio_session_id", Bundle.EMPTY);
        GET_AUDIO_SESSION_ID,

        val sessionCommand = SessionCommand(value, extras)
        /**
         * Toggle shuffle mode.
         *
         * Arguments:
         * - [ARG_VALUE] ([Boolean]): Whether to enable or disable shuffle mode
         */
        TOGGLE_SHUFFLE {
            override fun buildCommandButton(
                player: ExoPlayer,
                resources: Resources,
            ) = player.shuffleModeEnabled.let { shuffleModeEnabled ->
                val (icon, titleStringResId) = when (shuffleModeEnabled) {
                    true -> CommandButton.ICON_SHUFFLE_ON to R.string.shuffle_on
                    false -> CommandButton.ICON_SHUFFLE_OFF to R.string.shuffle_off
                }

                CommandButton.Builder(icon)
                    .setDisplayName(resources.getString(titleStringResId))
                    .setSessionCommand(
                        SessionCommand(
                            name,
                            bundleOf(ARG_VALUE to !shuffleModeEnabled),
                        )
                    )
                    .build()
            }
        },

        /**
         * Toggle repeat mode.
         *
         * Arguments:
         * - [ARG_VALUE] ([String]): The repeat mode
         */
        TOGGLE_REPEAT {
            override fun buildCommandButton(
                player: ExoPlayer,
                resources: Resources,
            ) = player.typedRepeatMode.let { repeatMode ->
                val (icon, titleStringResId) = when (repeatMode) {
                    RepeatMode.NONE -> CommandButton.ICON_REPEAT_OFF to R.string.repeat_off
                    RepeatMode.ALL -> CommandButton.ICON_REPEAT_ALL to R.string.repeat_all
                    RepeatMode.ONE -> CommandButton.ICON_REPEAT_ONE to R.string.repeat_one
                }

                CommandButton.Builder(icon)
                    .setDisplayName(resources.getString(titleStringResId))
                    .setSessionCommand(
                        SessionCommand(
                            name,
                            bundleOf(ARG_VALUE to repeatMode.next().name),
                        )
                    )
                    .build()
            }
        };

        val sessionCommand = SessionCommand(name, Bundle.EMPTY)

        open fun buildCommandButton(player: ExoPlayer, resources: Resources): CommandButton? = null

        companion object {
            const val ARG_VALUE = "value"
@@ -82,7 +146,7 @@ class PlaybackService : MediaLibraryService(), LifecycleOwner {

            fun fromCustomAction(
                customAction: String
            ) = entries.firstOrNull { it.value == customAction }
            ) = entries.firstOrNull { it.name == customAction }

            suspend fun MediaController.sendCustomCommand(
                customCommand: CustomCommand,
@@ -304,6 +368,22 @@ class PlaybackService : MediaLibraryService(), LifecycleOwner {
                    )
                }

                CustomCommand.TOGGLE_SHUFFLE -> {
                    args.getBoolean(CustomCommand.ARG_VALUE).let {
                        player.shuffleModeEnabled = it
                    }

                    SessionResult(SessionResult.RESULT_SUCCESS)
                }

                CustomCommand.TOGGLE_REPEAT -> {
                    args.getString(CustomCommand.ARG_VALUE)?.let {
                        player.typedRepeatMode = RepeatMode.valueOf(it)
                    }

                    SessionResult(SessionResult.RESULT_SUCCESS)
                }

                null -> SessionResult(SessionError.ERROR_NOT_SUPPORTED)
            }
        }
@@ -349,6 +429,7 @@ class PlaybackService : MediaLibraryService(), LifecycleOwner {
        )
            .setBitmapLoader(CoilBitmapLoader(this, lifecycleScope))
            .setSessionActivity(getSingleTopActivity())
            .setCustomLayout(getCustomLayout())
            .build()

        setMediaNotificationProvider(
@@ -391,6 +472,15 @@ class PlaybackService : MediaLibraryService(), LifecycleOwner {
                        NowPlayingAppWidgetProvider.update(this@PlaybackService)
                    }
                }

                // Update the shuffle and repeat buttons
                if (events.containsAny(
                        Player.EVENT_REPEAT_MODE_CHANGED,
                        Player.EVENT_SHUFFLE_MODE_ENABLED_CHANGED
                    )
                ) {
                    mediaLibrarySession.setCustomLayout(getCustomLayout())
                }
            }
        }
    }
@@ -458,4 +548,8 @@ class PlaybackService : MediaLibraryService(), LifecycleOwner {
        },
        PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT,
    )

    private fun getCustomLayout() = CustomCommand.entries.mapNotNull {
        it.buildCommandButton(player, resources)
    }
}
+7 −0
Original line number Diff line number Diff line
@@ -269,4 +269,11 @@
    <string name="provider">Provider</string>
    <string name="provider_format" translatable="false">%1$s (%2$s)</string>
    <string name="create_playlist_error_empty_name">No name specified</string>

    <!-- Player actions -->
    <string name="shuffle_on">Shuffle on</string>
    <string name="shuffle_off">Shuffle off</string>
    <string name="repeat_all">Repeat all</string>
    <string name="repeat_one">Repeat one</string>
    <string name="repeat_off">Repeat off</string>
</resources>