fix(android): fully close the app on swipe-away and avoid duplicate cookie DataStore

Initialize NewPipeDownloader once per process so recreating the Activity
no longer opens a second DataStore on spotube_cookies.preferences_pb.
Kill the process after PlaybackService shuts down on task removal so the
proxy server, MQTT client and singletons don't linger in the background.
This commit is contained in:
Kingkor Roy Tirtho 2026-10-30 13:35:39 +06:00
parent c058074cff
commit bba725a139
2 changed files with 15 additions and 2 deletions

View File

@ -82,10 +82,16 @@ class NewPipeDownloader(private val cookieJar: PersistentCookieJar) : Downloader
companion object { companion object {
private var instance: NewPipeDownloader? = null private var instance: NewPipeDownloader? = null
/**
* Safe to call on every Activity creation: the process (and so this singleton) outlives
* the Activity, and a second [PersistentCookieJar] would open a second DataStore on the
* same cookies file, which DataStore rejects.
*/
@Synchronized
fun init(paths: Paths) { fun init(paths: Paths) {
if (instance != null) return
val cookieJar = PersistentCookieJar(paths) val cookieJar = PersistentCookieJar(paths)
instance = NewPipeDownloader(cookieJar) instance = NewPipeDownloader(cookieJar).also { NewPipe.init(it) }
NewPipe.init(instance)
} }
} }
} }

View File

@ -23,6 +23,7 @@ import android.app.NotificationManager
import android.app.PendingIntent import android.app.PendingIntent
import android.content.Intent import android.content.Intent
import android.os.Build import android.os.Build
import android.os.Process
import android.util.Log import android.util.Log
import androidx.annotation.OptIn import androidx.annotation.OptIn
import androidx.core.app.NotificationCompat import androidx.core.app.NotificationCompat
@ -61,6 +62,8 @@ import spotube.composeapp.generated.resources.notification_playback_channel_name
@OptIn(UnstableApi::class) @OptIn(UnstableApi::class)
class PlaybackService : MediaLibraryService(), KoinComponent { class PlaybackService : MediaLibraryService(), KoinComponent {
private var taskRemoved = false
private val audioPlayer = inject<AudioPlayerInterface>().value as AudioPlayer private val audioPlayer = inject<AudioPlayerInterface>().value as AudioPlayer
private val audioPlayerQueue: AudioPlayerQueue by inject() private val audioPlayerQueue: AudioPlayerQueue by inject()
@ -161,9 +164,13 @@ class PlaybackService : MediaLibraryService(), KoinComponent {
removeSession(librarySession) removeSession(librarySession)
librarySession.release() librarySession.release()
super.onDestroy() super.onDestroy()
// Swiping the app out of recents should fully close it: Android otherwise keeps this
// process cached with the proxy server, MQTT client and every singleton still alive.
if (taskRemoved) Process.killProcess(Process.myPid())
} }
override fun onTaskRemoved(rootIntent: Intent?) { override fun onTaskRemoved(rootIntent: Intent?) {
taskRemoved = true
pauseAllPlayersAndStopSelf() pauseAllPlayersAndStopSelf()
} }