Skip to content

Commit

Permalink
player: Use PlayerNotificationManager
Browse files Browse the repository at this point in the history
* Integrates keepalive into media notification
* YOU DO NOT WANT TO KNOW HOW LONG IT TOOK TO DO THIS
  • Loading branch information
mikooomich committed Jan 27, 2025
1 parent 3f43e6a commit b5c1c3f
Show file tree
Hide file tree
Showing 7 changed files with 28 additions and 169 deletions.
1 change: 1 addition & 0 deletions app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,7 @@ dependencies {
implementation(libs.media3)
implementation(libs.media3.session)
implementation(libs.media3.okhttp)
implementation(libs.media3.ui)

implementation(libs.room.runtime)
ksp(libs.room.compiler)
Expand Down
8 changes: 0 additions & 8 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -154,14 +154,6 @@
</intent-filter>
</receiver>

<service
android:name=".playback.KeepAlive"
android:value="Experimental keep-alive service"
android:exported="false"
android:foregroundServiceType="specialUse"
tools:ignore="ExportedService">
</service>

<service
android:name=".playback.ExoDownloadService"
android:exported="false"
Expand Down
77 changes: 0 additions & 77 deletions app/src/main/java/com/dd3boh/outertune/playback/KeepAlive.kt

This file was deleted.

48 changes: 24 additions & 24 deletions app/src/main/java/com/dd3boh/outertune/playback/MusicService.kt
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.dd3boh.outertune.playback

import android.app.Notification
import android.app.PendingIntent
import android.content.ComponentName
import android.content.Context
Expand All @@ -10,7 +11,6 @@ import android.net.ConnectivityManager
import android.net.Uri
import android.os.Binder
import android.widget.Toast
import androidx.core.app.NotificationCompat
import androidx.core.content.getSystemService
import androidx.core.net.toUri
import androidx.datastore.preferences.core.edit
Expand Down Expand Up @@ -47,11 +47,11 @@ import androidx.media3.exoplayer.audio.DefaultAudioSink
import androidx.media3.exoplayer.audio.SilenceSkippingAudioProcessor
import androidx.media3.exoplayer.source.DefaultMediaSourceFactory
import androidx.media3.session.CommandButton
import androidx.media3.session.DefaultMediaNotificationProvider
import androidx.media3.session.MediaController
import androidx.media3.session.MediaLibraryService
import androidx.media3.session.MediaSession
import androidx.media3.session.SessionToken
import androidx.media3.ui.PlayerNotificationManager
import com.dd3boh.outertune.MainActivity
import com.dd3boh.outertune.R
import com.dd3boh.outertune.constants.AudioNormalizationKey
Expand Down Expand Up @@ -186,35 +186,14 @@ class MusicService : MediaLibraryService(),

lateinit var player: ExoPlayer
private lateinit var mediaSession: MediaLibrarySession
private lateinit var playerNotificationManager: PlayerNotificationManager

private var isAudioEffectSessionOpened = false

var consecutivePlaybackErr = 0

override fun onCreate() {
super.onCreate()
setMediaNotificationProvider(
DefaultMediaNotificationProvider(this, { NOTIFICATION_ID },
CHANNEL_ID, R.string.music_player)
.apply {
setSmallIcon(R.drawable.small_icon)
}
)

// FG keep alive
if (dataStore.get(KeepAliveKey, false)) {
try {
startService(Intent(this, KeepAlive::class.java))
} catch (e: Exception) {
reportException(e)
}
} else {
try {
stopService(Intent(this, KeepAlive::class.java))
} catch (e: Exception) {
reportException(e)
}
}

player = ExoPlayer.Builder(this)
.setMediaSourceFactory(DefaultMediaSourceFactory(createDataSourceFactory()))
Expand Down Expand Up @@ -389,6 +368,22 @@ class MusicService : MediaLibraryService(),
}
}
}

playerNotificationManager = PlayerNotificationManager.Builder(this, NOTIFICATION_ID, CHANNEL_ID)
.setNotificationListener(object : PlayerNotificationManager.NotificationListener {
override fun onNotificationPosted(notificationId: Int, notification: Notification, ongoing: Boolean) {
// FG keep alive
if (dataStore.get(KeepAliveKey, false)) {
startForeground(notificationId, notification)
} else {
stopForeground(notificationId)
}
}
})
.build()
playerNotificationManager.setPlayer(player)
playerNotificationManager.setSmallIcon(R.drawable.small_icon)
playerNotificationManager.setMediaSessionToken(mediaSession.platformToken)
}

fun initQueue() {
Expand Down Expand Up @@ -859,6 +854,11 @@ class MusicService : MediaLibraryService(),
database.rewriteAllQueues(data)
}
}

override fun onUpdateNotification(session: MediaSession, startInForegroundRequired: Boolean) {
// use default behaviour if keep alive is disabled
if (!dataStore.get(KeepAliveKey, false)) {
super.onUpdateNotification(session, startInForegroundRequired)
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,5 @@
package com.dd3boh.outertune.ui.screens.settings

import android.Manifest
import android.app.Activity
import android.content.Intent
import android.content.pm.PackageManager
import android.os.Build
import android.widget.Toast
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.windowInsetsPadding
import androidx.compose.foundation.rememberScrollState
Expand All @@ -27,7 +21,6 @@ import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBar
import androidx.compose.material3.TopAppBarScrollBehavior
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
Expand All @@ -36,7 +29,6 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.core.app.ActivityCompat
import androidx.navigation.NavController
import com.dd3boh.outertune.LocalPlayerAwareWindowInsets
import com.dd3boh.outertune.R
Expand All @@ -50,7 +42,6 @@ import com.dd3boh.outertune.constants.SkipOnErrorKey
import com.dd3boh.outertune.constants.SkipSilenceKey
import com.dd3boh.outertune.constants.StopMusicOnTaskClearKey
import com.dd3boh.outertune.constants.minPlaybackDurKey
import com.dd3boh.outertune.playback.KeepAlive
import com.dd3boh.outertune.ui.component.CounterDialog
import com.dd3boh.outertune.ui.component.EnumListPreference
import com.dd3boh.outertune.ui.component.IconButton
Expand All @@ -60,16 +51,13 @@ import com.dd3boh.outertune.ui.component.SwitchPreference
import com.dd3boh.outertune.ui.utils.backToMain
import com.dd3boh.outertune.utils.rememberEnumPreference
import com.dd3boh.outertune.utils.rememberPreference
import com.dd3boh.outertune.utils.reportException

@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun PlayerSettings(
navController: NavController,
scrollBehavior: TopAppBarScrollBehavior,
) {
val context = LocalContext.current

val (audioQuality, onAudioQualityChange) = rememberEnumPreference(key = AudioQualityKey, defaultValue = AudioQuality.AUTO)
val (persistentQueue, onPersistentQueueChange) = rememberPreference(key = PersistentQueueKey, defaultValue = true)
val (skipSilence, onSkipSilenceChange) = rememberPreference(key = SkipSilenceKey, defaultValue = false)
Expand All @@ -84,52 +72,6 @@ fun PlayerSettings(
mutableStateOf(false)
}

fun toggleKeepAlive(newValue: Boolean) {
// disable and request if disabled
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU
&& context.checkSelfPermission(Manifest.permission.POST_NOTIFICATIONS) != PackageManager.PERMISSION_GRANTED) {
onKeepAliveChange(false)
Toast.makeText(
context,
"Notification permission is required",
Toast.LENGTH_SHORT
).show()

ActivityCompat.requestPermissions(
context as Activity,
arrayOf( Manifest.permission.POST_NOTIFICATIONS), PackageManager.PERMISSION_GRANTED
)
return
}

if (keepAlive != newValue) {
onKeepAliveChange(newValue)
// start/stop service accordingly
if (newValue) {
try {
context.startService(Intent(context, KeepAlive::class.java))
} catch (e: Exception) {
reportException(e)
}
} else {
try {
context.stopService(Intent(context, KeepAlive::class.java))
} catch (e: Exception) {
reportException(e)
}
}
}
}

// reset if no permission
LaunchedEffect(keepAlive) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU
&& context.checkSelfPermission(Manifest.permission.POST_NOTIFICATIONS) != PackageManager.PERMISSION_GRANTED) {
onKeepAliveChange(false)
}
}


if (showMinPlaybackDur) {
CounterDialog(
title = stringResource(R.string.min_playback_duration),
Expand Down Expand Up @@ -233,7 +175,7 @@ fun PlayerSettings(
description = stringResource(R.string.keep_alive_description),
icon = { Icon(Icons.Rounded.NoCell, null) },
checked = keepAlive,
onCheckedChange = { toggleKeepAlive(it) }
onCheckedChange = onKeepAliveChange
)
}

Expand Down
2 changes: 1 addition & 1 deletion app/src/main/res/values/strings-ot.xml
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@
<string name="audio_offload">Enable offload</string>
<string name="audio_offload_description">Use the offload audio path for audio playback. Disabling this may increase power usage but can be useful if you experience issues with audio playback or post processing</string>
<string name="keep_alive_title">Prevent system killing app</string>
<string name="keep_alive_description">Use a foreground notification to prevent OuterTune from being randomly closed by the system.</string>
<string name="keep_alive_description">Use a persistent foreground service to prevent OuterTune from being closed in the background.</string>
<string name="persistent_queue_desc_ot">Restore previous open queue(s) when the app starts</string>
<string name="min_playback_duration">Minimum playback duration</string>
<string name="min_playback_duration_description">The minimum amount of a song that must be played before it is considered \"played\"</string>
Expand Down
1 change: 1 addition & 0 deletions gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ palette = { group = "androidx.palette", name = "palette", version = "1.0.0" }
media3 = { group = "androidx.media3", name = "media3-exoplayer", version.ref = "media3" }
media3-okhttp = { group = "androidx.media3", name = "media3-datasource-okhttp", version.ref = "media3" }
media3-session = { group = "androidx.media3", name = "media3-session", version.ref = "media3" }
media3-ui = { group = "androidx.media3", name = "media3-ui", version.ref = "media3" }

room-runtime = { group = "androidx.room", name = "room-runtime", version.ref = "room" }
room-compiler = { group = "androidx.room", name = "room-compiler", version.ref = "room" }
Expand Down

0 comments on commit b5c1c3f

Please sign in to comment.