mirror of
https://github.com/JustinZeus/Wholphin.git
synced 2026-07-08 23:51:21 +02:00
Fix some threading issues (#364)
Fixes thread used by changes from #359 Also create the device direct play profile lazily-ish off of the main thread because this can take some time to create. Should help address some of the "too much work" reported in #363, and also fixes the network-on-main exception in the logs on #363
This commit is contained in:
parent
56bf3cb7a0
commit
bddf51e076
10 changed files with 123 additions and 45 deletions
|
|
@ -34,6 +34,7 @@ import com.github.damontecres.wholphin.preferences.AppPreferences
|
|||
import com.github.damontecres.wholphin.preferences.DefaultUserConfiguration
|
||||
import com.github.damontecres.wholphin.preferences.UserPreferences
|
||||
import com.github.damontecres.wholphin.services.AppUpgradeHandler
|
||||
import com.github.damontecres.wholphin.services.DeviceProfileService
|
||||
import com.github.damontecres.wholphin.services.NavigationManager
|
||||
import com.github.damontecres.wholphin.services.PlaybackLifecycleObserver
|
||||
import com.github.damontecres.wholphin.services.ServerEventListener
|
||||
|
|
@ -45,8 +46,9 @@ import com.github.damontecres.wholphin.ui.nav.ApplicationContent
|
|||
import com.github.damontecres.wholphin.ui.nav.Destination
|
||||
import com.github.damontecres.wholphin.ui.theme.WholphinTheme
|
||||
import com.github.damontecres.wholphin.util.DebugLogTree
|
||||
import com.github.damontecres.wholphin.util.profile.createDeviceProfile
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
import okhttp3.OkHttpClient
|
||||
import org.jellyfin.sdk.model.serializer.toUUIDOrNull
|
||||
import timber.log.Timber
|
||||
|
|
@ -79,6 +81,9 @@ class MainActivity : AppCompatActivity() {
|
|||
@Inject
|
||||
lateinit var playbackLifecycleObserver: PlaybackLifecycleObserver
|
||||
|
||||
@Inject
|
||||
lateinit var deviceProfileService: DeviceProfileService
|
||||
|
||||
@OptIn(ExperimentalTvMaterial3Api::class)
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
|
|
@ -176,20 +181,19 @@ class MainActivity : AppCompatActivity() {
|
|||
}
|
||||
}
|
||||
}
|
||||
val deviceProfile =
|
||||
remember(current, preferences) {
|
||||
createDeviceProfile(
|
||||
this@MainActivity,
|
||||
preferences,
|
||||
LaunchedEffect(current, preferences) {
|
||||
withContext(Dispatchers.IO) {
|
||||
deviceProfileService.getOrCreateDeviceProfile(
|
||||
preferences.appPreferences.playbackPreferences,
|
||||
current?.server?.serverVersion,
|
||||
)
|
||||
}
|
||||
}
|
||||
ApplicationContent(
|
||||
user = current?.user,
|
||||
server = current?.server,
|
||||
navigationManager = navigationManager,
|
||||
preferences = preferences,
|
||||
deviceProfile = deviceProfile,
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,6 +2,8 @@ package com.github.damontecres.wholphin
|
|||
|
||||
import android.app.Application
|
||||
import android.os.Build
|
||||
import android.os.StrictMode
|
||||
import android.os.StrictMode.ThreadPolicy
|
||||
import android.util.Log
|
||||
import androidx.compose.runtime.Composer
|
||||
import androidx.compose.runtime.ExperimentalComposeRuntimeApi
|
||||
|
|
@ -19,6 +21,16 @@ class WholphinApplication : Application() {
|
|||
init {
|
||||
instance = this
|
||||
|
||||
if (BuildConfig.DEBUG) {
|
||||
StrictMode.setThreadPolicy(
|
||||
ThreadPolicy
|
||||
.Builder()
|
||||
.detectNetwork()
|
||||
.penaltyLog()
|
||||
.build(),
|
||||
)
|
||||
}
|
||||
|
||||
if (BuildConfig.DEBUG) {
|
||||
Timber.plant(Timber.DebugTree())
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,76 @@
|
|||
package com.github.damontecres.wholphin.services
|
||||
|
||||
import android.content.Context
|
||||
import com.github.damontecres.wholphin.preferences.PlaybackPreferences
|
||||
import com.github.damontecres.wholphin.util.profile.MediaCodecCapabilitiesTest
|
||||
import com.github.damontecres.wholphin.util.profile.createDeviceProfile
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.sync.Mutex
|
||||
import kotlinx.coroutines.sync.withLock
|
||||
import kotlinx.coroutines.withContext
|
||||
import org.jellyfin.sdk.model.ServerVersion
|
||||
import org.jellyfin.sdk.model.api.DeviceProfile
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Singleton
|
||||
class DeviceProfileService
|
||||
@Inject
|
||||
constructor(
|
||||
@param:ApplicationContext private val context: Context,
|
||||
) {
|
||||
private val mediaCodecCapabilitiesTest by lazy {
|
||||
// Created lazily below on the IO thread since it cn take time
|
||||
MediaCodecCapabilitiesTest(context)
|
||||
}
|
||||
private val mutex = Mutex()
|
||||
|
||||
private var configuration: DeviceProfileConfiguration? = null
|
||||
private var deviceProfile: DeviceProfile? = null
|
||||
|
||||
suspend fun getOrCreateDeviceProfile(
|
||||
prefs: PlaybackPreferences,
|
||||
serverVersion: ServerVersion?,
|
||||
): DeviceProfile =
|
||||
withContext(Dispatchers.IO) {
|
||||
mutex.withLock {
|
||||
val newConfig =
|
||||
DeviceProfileConfiguration(
|
||||
maxBitrate = prefs.maxBitrate.toInt(),
|
||||
isAC3Enabled = prefs.overrides.ac3Supported,
|
||||
downMixAudio = prefs.overrides.downmixStereo,
|
||||
assDirectPlay = prefs.overrides.directPlayAss,
|
||||
pgsDirectPlay = prefs.overrides.directPlayPgs,
|
||||
jellyfinTenEleven =
|
||||
serverVersion != null && serverVersion >= ServerVersion(10, 11, 0),
|
||||
)
|
||||
if (deviceProfile == null || this@DeviceProfileService.configuration != newConfig) {
|
||||
this@DeviceProfileService.configuration = newConfig
|
||||
this@DeviceProfileService.deviceProfile =
|
||||
createDeviceProfile(
|
||||
mediaTest = mediaCodecCapabilitiesTest,
|
||||
maxBitrate = newConfig.maxBitrate,
|
||||
isAC3Enabled = newConfig.isAC3Enabled,
|
||||
downMixAudio = newConfig.downMixAudio,
|
||||
assDirectPlay = newConfig.assDirectPlay,
|
||||
pgsDirectPlay = newConfig.pgsDirectPlay,
|
||||
jellyfinTenEleven = newConfig.jellyfinTenEleven,
|
||||
)
|
||||
}
|
||||
this@DeviceProfileService.deviceProfile!!
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The configuration used in [createDeviceProfile]
|
||||
*/
|
||||
data class DeviceProfileConfiguration(
|
||||
val maxBitrate: Int,
|
||||
val isAC3Enabled: Boolean,
|
||||
val downMixAudio: Boolean,
|
||||
val assDirectPlay: Boolean,
|
||||
val pgsDirectPlay: Boolean,
|
||||
val jellyfinTenEleven: Boolean,
|
||||
)
|
||||
|
|
@ -84,7 +84,7 @@ class RecommendedTvShowViewModel
|
|||
?: AppPreference.HomePageItems.defaultValue.toInt()
|
||||
try {
|
||||
val resumeItemsDeferred =
|
||||
viewModelScope.async {
|
||||
viewModelScope.async(Dispatchers.IO) {
|
||||
val resumeItemsRequest =
|
||||
GetResumeItemsRequest(
|
||||
parentId = parentId,
|
||||
|
|
@ -101,7 +101,7 @@ class RecommendedTvShowViewModel
|
|||
}
|
||||
|
||||
val nextUpItemsDeferred =
|
||||
viewModelScope.async {
|
||||
viewModelScope.async(Dispatchers.IO) {
|
||||
val nextUpRequest =
|
||||
GetNextUpRequest(
|
||||
parentId = parentId,
|
||||
|
|
|
|||
|
|
@ -12,7 +12,6 @@ import com.github.damontecres.wholphin.data.model.JellyfinUser
|
|||
import com.github.damontecres.wholphin.preferences.UserPreferences
|
||||
import com.github.damontecres.wholphin.services.NavigationManager
|
||||
import com.github.damontecres.wholphin.ui.components.ErrorMessage
|
||||
import org.jellyfin.sdk.model.api.DeviceProfile
|
||||
|
||||
/**
|
||||
* This is generally the root composable of the of the app
|
||||
|
|
@ -25,7 +24,6 @@ fun ApplicationContent(
|
|||
user: JellyfinUser?,
|
||||
navigationManager: NavigationManager,
|
||||
preferences: UserPreferences,
|
||||
deviceProfile: DeviceProfile,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
NavDisplay(
|
||||
|
|
@ -44,14 +42,12 @@ fun ApplicationContent(
|
|||
DestinationContent(
|
||||
destination = key,
|
||||
preferences = preferences,
|
||||
deviceProfile = deviceProfile,
|
||||
modifier = modifier.fillMaxSize(),
|
||||
)
|
||||
} else if (user != null && server != null) {
|
||||
NavDrawer(
|
||||
destination = key,
|
||||
preferences = preferences,
|
||||
deviceProfile = deviceProfile,
|
||||
user = user,
|
||||
server = server,
|
||||
modifier = modifier,
|
||||
|
|
|
|||
|
|
@ -32,7 +32,6 @@ import com.github.damontecres.wholphin.ui.setup.SwitchServerContent
|
|||
import com.github.damontecres.wholphin.ui.setup.SwitchUserContent
|
||||
import org.jellyfin.sdk.model.api.BaseItemKind
|
||||
import org.jellyfin.sdk.model.api.CollectionType
|
||||
import org.jellyfin.sdk.model.api.DeviceProfile
|
||||
import timber.log.Timber
|
||||
|
||||
/**
|
||||
|
|
@ -42,7 +41,6 @@ import timber.log.Timber
|
|||
fun DestinationContent(
|
||||
destination: Destination,
|
||||
preferences: UserPreferences,
|
||||
deviceProfile: DeviceProfile,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
when (destination) {
|
||||
|
|
@ -56,7 +54,6 @@ fun DestinationContent(
|
|||
->
|
||||
PlaybackPage(
|
||||
preferences = preferences,
|
||||
deviceProfile = deviceProfile,
|
||||
destination = destination,
|
||||
modifier = modifier,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -95,7 +95,6 @@ import kotlinx.coroutines.delay
|
|||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import org.jellyfin.sdk.model.api.CollectionType
|
||||
import org.jellyfin.sdk.model.api.DeviceProfile
|
||||
import timber.log.Timber
|
||||
import java.util.UUID
|
||||
import javax.inject.Inject
|
||||
|
|
@ -211,7 +210,6 @@ fun NavDrawer(
|
|||
preferences: UserPreferences,
|
||||
user: JellyfinUser,
|
||||
server: JellyfinServer,
|
||||
deviceProfile: DeviceProfile,
|
||||
modifier: Modifier = Modifier,
|
||||
viewModel: NavDrawerViewModel =
|
||||
hiltViewModel(
|
||||
|
|
@ -526,7 +524,6 @@ fun NavDrawer(
|
|||
DestinationContent(
|
||||
destination = destination,
|
||||
preferences = preferences,
|
||||
deviceProfile = deviceProfile,
|
||||
modifier =
|
||||
Modifier
|
||||
.fillMaxSize(),
|
||||
|
|
|
|||
|
|
@ -81,7 +81,6 @@ import com.github.damontecres.wholphin.util.LoadingState
|
|||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.launch
|
||||
import org.jellyfin.sdk.model.api.DeviceProfile
|
||||
import org.jellyfin.sdk.model.extensions.ticks
|
||||
import java.util.UUID
|
||||
import kotlin.time.Duration.Companion.milliseconds
|
||||
|
|
@ -94,7 +93,6 @@ import kotlin.time.Duration.Companion.seconds
|
|||
@Composable
|
||||
fun PlaybackPage(
|
||||
preferences: UserPreferences,
|
||||
deviceProfile: DeviceProfile,
|
||||
destination: Destination,
|
||||
modifier: Modifier = Modifier,
|
||||
viewModel: PlaybackViewModel = hiltViewModel(),
|
||||
|
|
@ -105,7 +103,7 @@ fun PlaybackPage(
|
|||
}
|
||||
}
|
||||
LaunchedEffect(destination) {
|
||||
viewModel.init(destination, deviceProfile, preferences)
|
||||
viewModel.init(destination, preferences)
|
||||
}
|
||||
|
||||
val loading by viewModel.loading.observeAsState(LoadingState.Loading)
|
||||
|
|
|
|||
|
|
@ -37,6 +37,7 @@ import com.github.damontecres.wholphin.preferences.ShowNextUpWhen
|
|||
import com.github.damontecres.wholphin.preferences.SkipSegmentBehavior
|
||||
import com.github.damontecres.wholphin.preferences.UserPreferences
|
||||
import com.github.damontecres.wholphin.services.DatePlayedService
|
||||
import com.github.damontecres.wholphin.services.DeviceProfileService
|
||||
import com.github.damontecres.wholphin.services.NavigationManager
|
||||
import com.github.damontecres.wholphin.services.PlayerFactory
|
||||
import com.github.damontecres.wholphin.services.PlaylistCreationResult
|
||||
|
|
@ -84,7 +85,6 @@ import org.jellyfin.sdk.api.client.extensions.videosApi
|
|||
import org.jellyfin.sdk.api.sockets.subscribe
|
||||
import org.jellyfin.sdk.model.DeviceInfo
|
||||
import org.jellyfin.sdk.model.api.BaseItemKind
|
||||
import org.jellyfin.sdk.model.api.DeviceProfile
|
||||
import org.jellyfin.sdk.model.api.MediaSegmentDto
|
||||
import org.jellyfin.sdk.model.api.MediaSegmentType
|
||||
import org.jellyfin.sdk.model.api.MediaStreamType
|
||||
|
|
@ -120,6 +120,7 @@ class PlaybackViewModel
|
|||
private val playerFactory: PlayerFactory,
|
||||
private val datePlayedService: DatePlayedService,
|
||||
private val deviceInfo: DeviceInfo,
|
||||
private val deviceProfileService: DeviceProfileService,
|
||||
) : ViewModel(),
|
||||
Player.Listener,
|
||||
AnalyticsListener {
|
||||
|
|
@ -145,7 +146,6 @@ class PlaybackViewModel
|
|||
val subtitleCues = MutableLiveData<List<Cue>>(listOf())
|
||||
|
||||
private lateinit var preferences: UserPreferences
|
||||
private lateinit var deviceProfile: DeviceProfile
|
||||
internal lateinit var itemId: UUID
|
||||
internal lateinit var item: BaseItem
|
||||
internal var forceTranscoding: Boolean = false
|
||||
|
|
@ -180,14 +180,12 @@ class PlaybackViewModel
|
|||
*/
|
||||
fun init(
|
||||
destination: Destination,
|
||||
deviceProfile: DeviceProfile,
|
||||
preferences: UserPreferences,
|
||||
) {
|
||||
nextUp.value = null
|
||||
this.preferences = preferences
|
||||
controllerViewState.hideMilliseconds =
|
||||
preferences.appPreferences.playbackPreferences.controllerTimeoutMs
|
||||
this.deviceProfile = deviceProfile
|
||||
this.forceTranscoding =
|
||||
(destination as? Destination.Playback)?.forceTranscoding ?: false
|
||||
val positionMs: Long
|
||||
|
|
@ -500,7 +498,10 @@ class PlaybackViewModel
|
|||
startTimeTicks = null,
|
||||
deviceProfile =
|
||||
if (playerBackend == PlayerBackend.EXO_PLAYER) {
|
||||
deviceProfile
|
||||
deviceProfileService.getOrCreateDeviceProfile(
|
||||
preferences.appPreferences.playbackPreferences,
|
||||
serverRepository.currentServer.value?.serverVersion,
|
||||
)
|
||||
} else {
|
||||
mpvDeviceProfile
|
||||
},
|
||||
|
|
|
|||
|
|
@ -2,10 +2,7 @@ package com.github.damontecres.wholphin.util.profile
|
|||
|
||||
// Adapted from https://github.com/jellyfin/jellyfin-androidtv/blob/v0.19.4/app/src/main/java/org/jellyfin/androidtv/util/profile/deviceProfile.kt
|
||||
|
||||
import android.content.Context
|
||||
import androidx.media3.common.MimeTypes
|
||||
import com.github.damontecres.wholphin.preferences.UserPreferences
|
||||
import org.jellyfin.sdk.model.ServerVersion
|
||||
import org.jellyfin.sdk.model.api.CodecType
|
||||
import org.jellyfin.sdk.model.api.DlnaProfileType
|
||||
import org.jellyfin.sdk.model.api.EncodingContext
|
||||
|
|
@ -46,21 +43,21 @@ val supportedAudioCodecs =
|
|||
Codec.Audio.VORBIS,
|
||||
)
|
||||
|
||||
fun createDeviceProfile(
|
||||
context: Context,
|
||||
userPreferences: UserPreferences,
|
||||
serverVersion: ServerVersion?,
|
||||
) = userPreferences.appPreferences.playbackPreferences.let { prefs ->
|
||||
createDeviceProfile(
|
||||
mediaTest = MediaCodecCapabilitiesTest(context),
|
||||
maxBitrate = prefs.maxBitrate.toInt(),
|
||||
isAC3Enabled = prefs.overrides.ac3Supported,
|
||||
downMixAudio = prefs.overrides.downmixStereo,
|
||||
assDirectPlay = prefs.overrides.directPlayAss,
|
||||
pgsDirectPlay = prefs.overrides.directPlayPgs,
|
||||
jellyfinTenEleven = serverVersion != null && serverVersion >= ServerVersion(10, 11, 0),
|
||||
)
|
||||
}
|
||||
// fun createDeviceProfile(
|
||||
// context: Context,
|
||||
// userPreferences: UserPreferences,
|
||||
// serverVersion: ServerVersion?,
|
||||
// ) = userPreferences.appPreferences.playbackPreferences.let { prefs ->
|
||||
// createDeviceProfile(
|
||||
// mediaTest = MediaCodecCapabilitiesTest(context),
|
||||
// maxBitrate = prefs.maxBitrate.toInt(),
|
||||
// isAC3Enabled = prefs.overrides.ac3Supported,
|
||||
// downMixAudio = prefs.overrides.downmixStereo,
|
||||
// assDirectPlay = prefs.overrides.directPlayAss,
|
||||
// pgsDirectPlay = prefs.overrides.directPlayPgs,
|
||||
// jellyfinTenEleven = serverVersion != null && serverVersion >= ServerVersion(10, 11, 0),
|
||||
// )
|
||||
// }
|
||||
|
||||
fun createDeviceProfile(
|
||||
mediaTest: MediaCodecCapabilitiesTest,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue