mirror of
https://github.com/JustinZeus/Wholphin.git
synced 2026-07-08 15:50:16 +02:00
Simplify how display mode is changed (#823)
## Description Simplifies the refresh rate/display mode change code by removing the live data go between. Also, instead of a `CountDownLatch` that blocks the thread, use a `Deferred` to suspend the coroutine instead. ### Related issues Might help with #769 ### Testing Tested on nvidia shield
This commit is contained in:
parent
045b689994
commit
733213d6a2
2 changed files with 37 additions and 27 deletions
|
|
@ -66,9 +66,12 @@ import com.github.damontecres.wholphin.ui.setup.SwitchUserContent
|
||||||
import com.github.damontecres.wholphin.ui.theme.WholphinTheme
|
import com.github.damontecres.wholphin.ui.theme.WholphinTheme
|
||||||
import com.github.damontecres.wholphin.ui.util.ProvideLocalClock
|
import com.github.damontecres.wholphin.ui.util.ProvideLocalClock
|
||||||
import com.github.damontecres.wholphin.util.DebugLogTree
|
import com.github.damontecres.wholphin.util.DebugLogTree
|
||||||
|
import com.github.damontecres.wholphin.util.ExceptionHandler
|
||||||
import dagger.hilt.android.AndroidEntryPoint
|
import dagger.hilt.android.AndroidEntryPoint
|
||||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.flow.firstOrNull
|
import kotlinx.coroutines.flow.firstOrNull
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
import okhttp3.OkHttpClient
|
import okhttp3.OkHttpClient
|
||||||
import org.jellyfin.sdk.model.api.BaseItemKind
|
import org.jellyfin.sdk.model.api.BaseItemKind
|
||||||
import org.jellyfin.sdk.model.serializer.toUUIDOrNull
|
import org.jellyfin.sdk.model.serializer.toUUIDOrNull
|
||||||
|
|
@ -126,19 +129,12 @@ class MainActivity : AppCompatActivity() {
|
||||||
@OptIn(ExperimentalTvMaterial3Api::class)
|
@OptIn(ExperimentalTvMaterial3Api::class)
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
|
instance = this
|
||||||
Timber.i("MainActivity.onCreate: savedInstanceState is null=${savedInstanceState == null}")
|
Timber.i("MainActivity.onCreate: savedInstanceState is null=${savedInstanceState == null}")
|
||||||
lifecycle.addObserver(playbackLifecycleObserver)
|
lifecycle.addObserver(playbackLifecycleObserver)
|
||||||
if (savedInstanceState == null) {
|
if (savedInstanceState == null) {
|
||||||
appUpgradeHandler.copySubfont(false)
|
appUpgradeHandler.copySubfont(false)
|
||||||
}
|
}
|
||||||
refreshRateService.refreshRateMode.observe(this) { modeId ->
|
|
||||||
// Listen for refresh rate changes
|
|
||||||
val attrs = window.attributes
|
|
||||||
if (attrs.preferredDisplayModeId != modeId) {
|
|
||||||
Timber.d("Switch preferredDisplayModeId to %s", modeId)
|
|
||||||
window.attributes = attrs.apply { preferredDisplayModeId = modeId }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
viewModel.serverRepository.currentUser.observe(this) { user ->
|
viewModel.serverRepository.currentUser.observe(this) { user ->
|
||||||
if (user?.hasPin == true) {
|
if (user?.hasPin == true) {
|
||||||
window?.setFlags(
|
window?.setFlags(
|
||||||
|
|
@ -384,6 +380,16 @@ class MainActivity : AppCompatActivity() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun changeDisplayMode(modeId: Int) {
|
||||||
|
lifecycleScope.launch(Dispatchers.Main + ExceptionHandler()) {
|
||||||
|
val attrs = window.attributes
|
||||||
|
if (attrs.preferredDisplayModeId != modeId) {
|
||||||
|
Timber.d("Switch preferredDisplayModeId to %s", modeId)
|
||||||
|
window.attributes = attrs.apply { preferredDisplayModeId = modeId }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
const val INTENT_ITEM_ID = "itemId"
|
const val INTENT_ITEM_ID = "itemId"
|
||||||
const val INTENT_ITEM_TYPE = "itemType"
|
const val INTENT_ITEM_TYPE = "itemType"
|
||||||
|
|
@ -391,6 +397,9 @@ class MainActivity : AppCompatActivity() {
|
||||||
const val INTENT_EPISODE_NUMBER = "epNum"
|
const val INTENT_EPISODE_NUMBER = "epNum"
|
||||||
const val INTENT_SEASON_NUMBER = "seaNum"
|
const val INTENT_SEASON_NUMBER = "seaNum"
|
||||||
const val INTENT_SEASON_ID = "seaId"
|
const val INTENT_SEASON_ID = "seaId"
|
||||||
|
|
||||||
|
lateinit var instance: MainActivity
|
||||||
|
private set
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -6,17 +6,17 @@ import android.os.Build
|
||||||
import android.os.Handler
|
import android.os.Handler
|
||||||
import android.os.Looper
|
import android.os.Looper
|
||||||
import android.view.Display
|
import android.view.Display
|
||||||
import androidx.lifecycle.LiveData
|
import com.github.damontecres.wholphin.MainActivity
|
||||||
import com.github.damontecres.wholphin.ui.setValueOnMain
|
|
||||||
import com.github.damontecres.wholphin.ui.showToast
|
import com.github.damontecres.wholphin.ui.showToast
|
||||||
import com.github.damontecres.wholphin.util.EqualityMutableLiveData
|
|
||||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||||
|
import kotlinx.coroutines.CompletableDeferred
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.delay
|
import kotlinx.coroutines.delay
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
|
import kotlinx.coroutines.withTimeoutOrNull
|
||||||
import org.jellyfin.sdk.model.api.MediaStream
|
import org.jellyfin.sdk.model.api.MediaStream
|
||||||
import org.jellyfin.sdk.model.api.MediaStreamType
|
import org.jellyfin.sdk.model.api.MediaStreamType
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import java.util.concurrent.CountDownLatch
|
|
||||||
import java.util.concurrent.TimeUnit
|
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
import javax.inject.Singleton
|
import javax.inject.Singleton
|
||||||
import kotlin.math.roundToInt
|
import kotlin.math.roundToInt
|
||||||
|
|
@ -30,7 +30,6 @@ class RefreshRateService
|
||||||
) {
|
) {
|
||||||
private val displayManager = context.getSystemService(Context.DISPLAY_SERVICE) as DisplayManager
|
private val displayManager = context.getSystemService(Context.DISPLAY_SERVICE) as DisplayManager
|
||||||
private val display = displayManager.getDisplay(Display.DEFAULT_DISPLAY)
|
private val display = displayManager.getDisplay(Display.DEFAULT_DISPLAY)
|
||||||
private val originalMode = display.mode
|
|
||||||
|
|
||||||
val supportedDisplayModes get() = display.supportedModes.orEmpty()
|
val supportedDisplayModes get() = display.supportedModes.orEmpty()
|
||||||
|
|
||||||
|
|
@ -44,9 +43,6 @@ class RefreshRateService
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
private val _refreshRateMode = EqualityMutableLiveData<Int>(originalMode.modeId)
|
|
||||||
val refreshRateMode: LiveData<Int> = _refreshRateMode
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Find the best display mode for the given stream and signal to change to it
|
* Find the best display mode for the given stream and signal to change to it
|
||||||
*/
|
*/
|
||||||
|
|
@ -54,10 +50,10 @@ class RefreshRateService
|
||||||
stream: MediaStream,
|
stream: MediaStream,
|
||||||
switchRefreshRate: Boolean,
|
switchRefreshRate: Boolean,
|
||||||
switchResolution: Boolean,
|
switchResolution: Boolean,
|
||||||
) {
|
) = withContext(Dispatchers.IO) {
|
||||||
if (!switchRefreshRate && !switchResolution) {
|
if (!switchRefreshRate && !switchResolution) {
|
||||||
Timber.v("Not switching either refresh rate nor resolution")
|
Timber.v("Not switching either refresh rate nor resolution")
|
||||||
return
|
return@withContext
|
||||||
}
|
}
|
||||||
val currentDisplayMode = display.mode
|
val currentDisplayMode = display.mode
|
||||||
require(stream.type == MediaStreamType.VIDEO) { "Stream is not video" }
|
require(stream.type == MediaStreamType.VIDEO) { "Stream is not video" }
|
||||||
|
|
@ -67,7 +63,7 @@ class RefreshRateService
|
||||||
if (switchRefreshRate) stream.realFrameRate else currentDisplayMode.refreshRate
|
if (switchRefreshRate) stream.realFrameRate else currentDisplayMode.refreshRate
|
||||||
if (width == null || height == null || frameRate == null) {
|
if (width == null || height == null || frameRate == null) {
|
||||||
Timber.w("Video stream missing required info: width=%s, height=%s, frameRate=%s", width, height, frameRate)
|
Timber.w("Video stream missing required info: width=%s, height=%s, frameRate=%s", width, height, frameRate)
|
||||||
return
|
return@withContext
|
||||||
}
|
}
|
||||||
Timber.d("Getting refresh rate for: width=%s, height=%s, frameRate=%s", width, height, frameRate)
|
Timber.d("Getting refresh rate for: width=%s, height=%s, frameRate=%s", width, height, frameRate)
|
||||||
val targetMode =
|
val targetMode =
|
||||||
|
|
@ -86,14 +82,20 @@ class RefreshRateService
|
||||||
listener,
|
listener,
|
||||||
Handler(Looper.myLooper() ?: Looper.getMainLooper()),
|
Handler(Looper.myLooper() ?: Looper.getMainLooper()),
|
||||||
)
|
)
|
||||||
_refreshRateMode.setValueOnMain(targetMode.modeId)
|
|
||||||
try {
|
try {
|
||||||
if (!listener.latch.await(5, TimeUnit.SECONDS)) {
|
MainActivity.instance.changeDisplayMode(targetMode.modeId)
|
||||||
|
val result =
|
||||||
|
withTimeoutOrNull(5.seconds) {
|
||||||
|
listener.deferred.await()
|
||||||
|
}
|
||||||
|
if (result == null) {
|
||||||
Timber.w("Timed out waiting for display change")
|
Timber.w("Timed out waiting for display change")
|
||||||
showToast(context, "Refresh rate switch is taking a long time")
|
showToast(context, "Refresh rate switch is taking a long time")
|
||||||
}
|
}
|
||||||
} catch (ex: InterruptedException) {
|
} catch (ex: Exception) {
|
||||||
Timber.w(ex, "Exception waiting for refresh rate switch")
|
Timber.w(ex, "Exception waiting for refresh rate switch")
|
||||||
|
} finally {
|
||||||
|
displayManager.unregisterDisplayListener(listener)
|
||||||
}
|
}
|
||||||
val targetRate = (targetMode.refreshRate * 1000).roundToInt()
|
val targetRate = (targetMode.refreshRate * 1000).roundToInt()
|
||||||
val isSeamless =
|
val isSeamless =
|
||||||
|
|
@ -110,7 +112,6 @@ class RefreshRateService
|
||||||
// Wait the recommended 2 seconds (https://developer.android.com/media/optimize/performance/frame-rate)
|
// Wait the recommended 2 seconds (https://developer.android.com/media/optimize/performance/frame-rate)
|
||||||
delay(2.seconds)
|
delay(2.seconds)
|
||||||
}
|
}
|
||||||
displayManager.unregisterDisplayListener(listener)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -118,13 +119,13 @@ class RefreshRateService
|
||||||
* Reset the display mode to the original
|
* Reset the display mode to the original
|
||||||
*/
|
*/
|
||||||
fun resetRefreshRate() {
|
fun resetRefreshRate() {
|
||||||
_refreshRateMode.value = originalMode.modeId
|
MainActivity.instance.changeDisplayMode(0)
|
||||||
}
|
}
|
||||||
|
|
||||||
private class Listener(
|
private class Listener(
|
||||||
val displayId: Int,
|
val displayId: Int,
|
||||||
) : DisplayManager.DisplayListener {
|
) : DisplayManager.DisplayListener {
|
||||||
val latch = CountDownLatch(1)
|
val deferred = CompletableDeferred<Unit>()
|
||||||
|
|
||||||
override fun onDisplayAdded(displayId: Int) {
|
override fun onDisplayAdded(displayId: Int) {
|
||||||
}
|
}
|
||||||
|
|
@ -132,7 +133,7 @@ class RefreshRateService
|
||||||
override fun onDisplayChanged(displayId: Int) {
|
override fun onDisplayChanged(displayId: Int) {
|
||||||
if (displayId == this.displayId) {
|
if (displayId == this.displayId) {
|
||||||
Timber.v("Got display change for $displayId")
|
Timber.v("Got display change for $displayId")
|
||||||
latch.countDown()
|
deferred.complete(Unit)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue