Fix network on main exception trying to play theme songs (#111)

Fetch the theme song info on the IO thread

This would throw an exception on some devices, but it would be caught
without affecting the app, other than just not playing theme music.
This commit is contained in:
damontecres 2025-10-29 20:07:00 -04:00 committed by GitHub
parent 84ee57225b
commit f76556f90e
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 19 additions and 18 deletions

View file

@ -146,7 +146,7 @@ class SeriesViewModel
seriesId: UUID, seriesId: UUID,
playThemeSongs: ThemeSongVolume, playThemeSongs: ThemeSongVolume,
) { ) {
viewModelScope.launch(ExceptionHandler()) { viewModelScope.launchIO {
val themeSongs = api.libraryApi.getThemeSongs(seriesId).content val themeSongs = api.libraryApi.getThemeSongs(seriesId).content
themeSongs.items.firstOrNull()?.let { theme -> themeSongs.items.firstOrNull()?.let { theme ->
theme.mediaSources?.firstOrNull()?.let { source -> theme.mediaSources?.firstOrNull()?.let { source ->
@ -161,7 +161,7 @@ class SeriesViewModel
Codec.Audio.FLAC, Codec.Audio.FLAC,
), ),
) )
Timber.Forest.v("Found theme song for series $seriesId") Timber.v("Found theme song for series $seriesId")
withContext(Dispatchers.Main) { withContext(Dispatchers.Main) {
themeSongPlayer.play(playThemeSongs, url) themeSongPlayer.play(playThemeSongs, url)
addCloseable { addCloseable {

View file

@ -173,10 +173,11 @@ class PlaybackViewModel
this.itemId = itemId this.itemId = itemId
val item = destination.item val item = destination.item
viewModelScope.launch( viewModelScope.launch(
LoadingExceptionHandler( Dispatchers.IO +
loading, LoadingExceptionHandler(
"Error preparing for playback for ${destination.itemId}", loading,
) + Dispatchers.IO, "Error preparing for playback for ${destination.itemId}",
),
) { ) {
val queriedItem = item?.data ?: api.userLibraryApi.getItem(itemId).content val queriedItem = item?.data ?: api.userLibraryApi.getItem(itemId).content
val base = val base =
@ -493,7 +494,7 @@ class PlaybackViewModel
subtitleIndex = subtitleIndex ?: TrackIndex.DISABLED, subtitleIndex = subtitleIndex ?: TrackIndex.DISABLED,
) )
if (userInitiated) { if (userInitiated) {
viewModelScope.launch(Dispatchers.IO + ExceptionHandler()) { viewModelScope.launchIO {
Timber.v("Saving user initiated item playback: %s", itemPlayback) Timber.v("Saving user initiated item playback: %s", itemPlayback)
val updated = itemPlaybackRepository.saveItemPlayback(itemPlayback) val updated = itemPlaybackRepository.saveItemPlayback(itemPlayback)
withContext(Dispatchers.Main) { withContext(Dispatchers.Main) {
@ -560,7 +561,7 @@ class PlaybackViewModel
} }
fun changeAudioStream(index: Int) { fun changeAudioStream(index: Int) {
viewModelScope.launch(ExceptionHandler()) { viewModelScope.launchIO {
changeStreams( changeStreams(
dto, dto,
currentItemPlayback.value!!, currentItemPlayback.value!!,
@ -573,7 +574,7 @@ class PlaybackViewModel
} }
fun changeSubtitleStream(index: Int?): Job = fun changeSubtitleStream(index: Int?): Job =
viewModelScope.launch(ExceptionHandler()) { viewModelScope.launchIO {
changeStreams( changeStreams(
dto, dto,
currentItemPlayback.value!!, currentItemPlayback.value!!,
@ -604,7 +605,7 @@ class PlaybackViewModel
object : Player.Listener { object : Player.Listener {
override fun onPlaybackStateChanged(playbackState: Int) { override fun onPlaybackStateChanged(playbackState: Int) {
if (playbackState == Player.STATE_ENDED) { if (playbackState == Player.STATE_ENDED) {
viewModelScope.launch(Dispatchers.IO + ExceptionHandler()) { viewModelScope.launchIO {
val nextItem = playlist.peek() val nextItem = playlist.peek()
Timber.v("Setting next up to ${nextItem?.id}") Timber.v("Setting next up to ${nextItem?.id}")
withContext(Dispatchers.Main) { withContext(Dispatchers.Main) {
@ -625,7 +626,7 @@ class PlaybackViewModel
private fun listenForSegments() { private fun listenForSegments() {
segmentJob?.cancel() segmentJob?.cancel()
segmentJob = segmentJob =
viewModelScope.launch(ExceptionHandler() + Dispatchers.IO) { viewModelScope.launchIO {
val prefs = preferences.appPreferences.playbackPreferences val prefs = preferences.appPreferences.playbackPreferences
val segments by api.mediaSegmentsApi.getItemSegments(itemId) val segments by api.mediaSegmentsApi.getItemSegments(itemId)
if (segments.items.isNotEmpty()) { if (segments.items.isNotEmpty()) {
@ -700,7 +701,7 @@ class PlaybackViewModel
fun playUpNextUp() { fun playUpNextUp() {
playlist.value?.let { playlist.value?.let {
if (it.hasNext()) { if (it.hasNext()) {
viewModelScope.launch(ExceptionHandler()) { viewModelScope.launchIO {
cancelUpNextEpisode() cancelUpNextEpisode()
val item = it.getAndAdvance() val item = it.getAndAdvance()
val played = play(item.data, 0) val played = play(item.data, 0)
@ -715,7 +716,7 @@ class PlaybackViewModel
fun playPrevious() { fun playPrevious() {
playlist.value?.let { playlist.value?.let {
if (it.hasPrevious()) { if (it.hasPrevious()) {
viewModelScope.launch(ExceptionHandler()) { viewModelScope.launchIO {
cancelUpNextEpisode() cancelUpNextEpisode()
val item = it.getPreviousAndReverse() val item = it.getPreviousAndReverse()
val played = play(item.data, 0) val played = play(item.data, 0)
@ -727,13 +728,13 @@ class PlaybackViewModel
} }
} }
fun cancelUpNextEpisode() { suspend fun cancelUpNextEpisode() {
nextUp.value = null nextUp.setValueOnMain(null)
} }
fun playItemInPlaylist(item: BaseItem) { fun playItemInPlaylist(item: BaseItem) {
playlist.value?.let { playlist -> playlist.value?.let { playlist ->
viewModelScope.launch(ExceptionHandler()) { viewModelScope.launchIO {
val toPlay = playlist.advanceTo(item.id) val toPlay = playlist.advanceTo(item.id)
if (toPlay != null) { if (toPlay != null) {
val played = play(toPlay.data, 0) val played = play(toPlay.data, 0)

View file

@ -3,12 +3,12 @@ package com.github.damontecres.wholphin.util
import android.content.Context import android.content.Context
import android.widget.Toast import android.widget.Toast
import com.github.damontecres.wholphin.hilt.IoCoroutineScope import com.github.damontecres.wholphin.hilt.IoCoroutineScope
import com.github.damontecres.wholphin.ui.launchIO
import com.github.damontecres.wholphin.ui.showToast import com.github.damontecres.wholphin.ui.showToast
import dagger.hilt.android.qualifiers.ApplicationContext import dagger.hilt.android.qualifiers.ApplicationContext
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.launch
import org.jellyfin.sdk.api.client.ApiClient import org.jellyfin.sdk.api.client.ApiClient
import org.jellyfin.sdk.api.client.extensions.sessionApi import org.jellyfin.sdk.api.client.extensions.sessionApi
import org.jellyfin.sdk.api.sockets.subscribe import org.jellyfin.sdk.api.sockets.subscribe
@ -32,7 +32,7 @@ class ServerEventListener
} }
fun init() { fun init() {
scope.launch(ExceptionHandler()) { scope.launchIO {
api.sessionApi.postCapabilities( api.sessionApi.postCapabilities(
playableMediaTypes = listOf(MediaType.VIDEO), playableMediaTypes = listOf(MediaType.VIDEO),
supportedCommands = supportedCommands =