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.
This commit is contained in:
damontecres 2025-10-18 20:59:38 -04:00 committed by GitHub
parent 5bb46bf34b
commit 93f7abfa3b
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 82 additions and 39 deletions

View file

@ -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()
}
}

View file

@ -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>(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 {

View file

@ -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()
}
}