From 93f7abfa3ba9e8766c5534d8d87cf04fc20644bf Mon Sep 17 00:00:00 2001 From: damontecres <154766448+damontecres@users.noreply.github.com> Date: Sat, 18 Oct 2025 20:59:38 -0400 Subject: [PATCH] Fix issue where theme songs played when the app is backgrounded (#34) Previously if the app is playing a show's theme song and the app is backgrounded, such as by pressing Home, the music would continue. This PR fixes that. --- .../damontecres/wholphin/MainActivity.kt | 9 +++ .../wholphin/ui/detail/SeriesViewModel.kt | 44 ++---------- .../wholphin/util/ThemeSongPlayer.kt | 68 +++++++++++++++++++ 3 files changed, 82 insertions(+), 39 deletions(-) create mode 100644 app/src/main/java/com/github/damontecres/wholphin/util/ThemeSongPlayer.kt diff --git a/app/src/main/java/com/github/damontecres/wholphin/MainActivity.kt b/app/src/main/java/com/github/damontecres/wholphin/MainActivity.kt index 67277279..5483090c 100644 --- a/app/src/main/java/com/github/damontecres/wholphin/MainActivity.kt +++ b/app/src/main/java/com/github/damontecres/wholphin/MainActivity.kt @@ -37,6 +37,7 @@ import com.github.damontecres.wholphin.ui.nav.NavigationManager import com.github.damontecres.wholphin.ui.theme.WholphinTheme import com.github.damontecres.wholphin.util.AppUpgradeHandler import com.github.damontecres.wholphin.util.ExceptionHandler +import com.github.damontecres.wholphin.util.ThemeSongPlayer import com.github.damontecres.wholphin.util.UpdateChecker import com.github.damontecres.wholphin.util.profile.createDeviceProfile import dagger.hilt.android.AndroidEntryPoint @@ -68,6 +69,9 @@ class MainActivity : AppCompatActivity() { @Inject lateinit var appUpgradeHandler: AppUpgradeHandler + @Inject + lateinit var themeSongPlayer: ThemeSongPlayer + @OptIn(ExperimentalTvMaterial3Api::class) override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) @@ -164,4 +168,9 @@ class MainActivity : AppCompatActivity() { } } } + + override fun onStop() { + super.onStop() + themeSongPlayer.stop() + } } diff --git a/app/src/main/java/com/github/damontecres/wholphin/ui/detail/SeriesViewModel.kt b/app/src/main/java/com/github/damontecres/wholphin/ui/detail/SeriesViewModel.kt index 266322d3..ffea0452 100644 --- a/app/src/main/java/com/github/damontecres/wholphin/ui/detail/SeriesViewModel.kt +++ b/app/src/main/java/com/github/damontecres/wholphin/ui/detail/SeriesViewModel.kt @@ -5,18 +5,12 @@ import android.widget.Toast import androidx.annotation.OptIn import androidx.lifecycle.MutableLiveData import androidx.lifecycle.viewModelScope -import androidx.media3.common.MediaItem -import androidx.media3.common.Player import androidx.media3.common.util.UnstableApi -import androidx.media3.datasource.okhttp.OkHttpDataSource -import androidx.media3.exoplayer.ExoPlayer -import androidx.media3.exoplayer.source.DefaultMediaSourceFactory import com.github.damontecres.wholphin.data.ChosenStreams import com.github.damontecres.wholphin.data.ItemPlaybackRepository import com.github.damontecres.wholphin.data.model.BaseItem import com.github.damontecres.wholphin.data.model.ItemPlayback import com.github.damontecres.wholphin.data.model.Person -import com.github.damontecres.wholphin.hilt.AuthOkHttpClient import com.github.damontecres.wholphin.preferences.ThemeSongVolume import com.github.damontecres.wholphin.preferences.UserPreferences import com.github.damontecres.wholphin.ui.launchIO @@ -30,6 +24,7 @@ import com.github.damontecres.wholphin.util.GetEpisodesRequestHandler import com.github.damontecres.wholphin.util.GetItemsRequestHandler import com.github.damontecres.wholphin.util.LoadingExceptionHandler import com.github.damontecres.wholphin.util.LoadingState +import com.github.damontecres.wholphin.util.ThemeSongPlayer import com.github.damontecres.wholphin.util.profile.Codec import dagger.hilt.android.lifecycle.HiltViewModel import dagger.hilt.android.qualifiers.ApplicationContext @@ -38,7 +33,6 @@ import kotlinx.coroutines.Job import kotlinx.coroutines.async import kotlinx.coroutines.launch import kotlinx.coroutines.withContext -import okhttp3.OkHttpClient import org.jellyfin.sdk.api.client.ApiClient import org.jellyfin.sdk.api.client.extensions.libraryApi import org.jellyfin.sdk.api.client.extensions.playStateApi @@ -62,11 +56,10 @@ class SeriesViewModel constructor( api: ApiClient, @param:ApplicationContext val context: Context, - @param:AuthOkHttpClient private val okHttpClient: OkHttpClient, private val navigationManager: NavigationManager, private val itemPlaybackRepository: ItemPlaybackRepository, + private val themeSongPlayer: ThemeSongPlayer, ) : ItemViewModel(api) { - private var player: Player? = null private lateinit var seriesId: UUID private lateinit var prefs: UserPreferences val loading = MutableLiveData(LoadingState.Loading) @@ -117,18 +110,6 @@ class SeriesViewModel */ @OptIn(UnstableApi::class) private fun maybePlayThemeSong(playThemeSongs: ThemeSongVolume) { - val volume = - when (playThemeSongs) { - ThemeSongVolume.UNRECOGNIZED, - ThemeSongVolume.DISABLED, - -> return - - ThemeSongVolume.LOWEST -> .05f - ThemeSongVolume.LOW -> .1f - ThemeSongVolume.MEDIUM -> .25f - ThemeSongVolume.HIGH -> .5f - ThemeSongVolume.HIGHEST -> 75f - } viewModelScope.launch(ExceptionHandler()) { val themeSongs = api.libraryApi.getThemeSongs(seriesId).content themeSongs.items.firstOrNull()?.let { theme -> @@ -146,24 +127,10 @@ class SeriesViewModel ) Timber.Forest.v("Found theme song for series $seriesId") withContext(Dispatchers.Main) { - val player = - ExoPlayer - .Builder(context) - .setMediaSourceFactory( - DefaultMediaSourceFactory( - OkHttpDataSource.Factory(okHttpClient), - ), - ).build() - .apply { - this.volume = volume - playWhenReady = true - this@SeriesViewModel.player = this - } + themeSongPlayer.play(playThemeSongs, url) addCloseable { - player.release() + themeSongPlayer.stop() } - player.setMediaItem(MediaItem.fromUri(url)) - player.prepare() } } } @@ -171,8 +138,7 @@ class SeriesViewModel } fun release() { - player?.release() - player = null + themeSongPlayer.stop() } private suspend fun getSeasons(item: BaseItem): ItemListAndMapping { diff --git a/app/src/main/java/com/github/damontecres/wholphin/util/ThemeSongPlayer.kt b/app/src/main/java/com/github/damontecres/wholphin/util/ThemeSongPlayer.kt new file mode 100644 index 00000000..de334ba3 --- /dev/null +++ b/app/src/main/java/com/github/damontecres/wholphin/util/ThemeSongPlayer.kt @@ -0,0 +1,68 @@ +package com.github.damontecres.wholphin.util + +import android.content.Context +import androidx.annotation.OptIn +import androidx.media3.common.MediaItem +import androidx.media3.common.Player +import androidx.media3.common.util.UnstableApi +import androidx.media3.datasource.okhttp.OkHttpDataSource +import androidx.media3.exoplayer.ExoPlayer +import androidx.media3.exoplayer.source.DefaultMediaSourceFactory +import com.github.damontecres.wholphin.hilt.AuthOkHttpClient +import com.github.damontecres.wholphin.preferences.ThemeSongVolume +import dagger.hilt.android.qualifiers.ApplicationContext +import okhttp3.OkHttpClient +import javax.inject.Inject +import javax.inject.Singleton + +/** + * Simple service to play theme song music + */ +@Singleton +class ThemeSongPlayer + @Inject + constructor( + @param:ApplicationContext val context: Context, + @param:AuthOkHttpClient val okHttpClient: OkHttpClient, + ) { + private var player: Player? = null + + @OptIn(UnstableApi::class) + fun play( + volume: ThemeSongVolume, + url: String, + ) { + val volumeLevel = + when (volume) { + ThemeSongVolume.UNRECOGNIZED, + ThemeSongVolume.DISABLED, + -> return + + ThemeSongVolume.LOWEST -> .05f + ThemeSongVolume.LOW -> .1f + ThemeSongVolume.MEDIUM -> .25f + ThemeSongVolume.HIGH -> .5f + ThemeSongVolume.HIGHEST -> 75f + } + val player = + ExoPlayer + .Builder(context) + .setMediaSourceFactory( + DefaultMediaSourceFactory( + OkHttpDataSource.Factory(okHttpClient), + ), + ).build() + .apply { + this.volume = volumeLevel + playWhenReady = true + } + player.setMediaItem(MediaItem.fromUri(url)) + player.prepare() + this.player = player + } + + fun stop() { + player?.stop() + player?.release() + } + }