From 6710c87892e8ba0ec3920bfa23f18da8b8718442 Mon Sep 17 00:00:00 2001 From: edde746 <86283021+edde746@users.noreply.github.com> Date: Sat, 25 Jul 2026 17:37:32 +0200 Subject: [PATCH] fix(android): accept denied audio-focus resumes --- .../com/edde746/plezy/mpv/MpvPlayerCore.kt | 32 ++++++------------- .../edde746/plezy/mpv/MpvPlayerPluginTest.kt | 11 ++++--- 2 files changed, 15 insertions(+), 28 deletions(-) diff --git a/android/app/src/main/kotlin/com/edde746/plezy/mpv/MpvPlayerCore.kt b/android/app/src/main/kotlin/com/edde746/plezy/mpv/MpvPlayerCore.kt index d21589a6..bf88fb89 100644 --- a/android/app/src/main/kotlin/com/edde746/plezy/mpv/MpvPlayerCore.kt +++ b/android/app/src/main/kotlin/com/edde746/plezy/mpv/MpvPlayerCore.kt @@ -720,17 +720,12 @@ class MpvPlayerCore private constructor( } } - private fun completeFailedPublicResume( - intent: PublicPauseIntent, - failure: Throwable, - onComplete: ((Result) -> Unit)? - ) { - rollbackFailedPublicPauseIntent(intent) + private fun completePublicResumeNoOp(onComplete: ((Result) -> Unit)?) { runOnMain { val completion: Result = if (disposing || !isInitialized || !scope.isActive) { Result.failure(CancellationException("MPV core unavailable")) } else { - Result.failure(failure) + Result.success(Unit) } onComplete?.invoke(completion) } @@ -877,12 +872,8 @@ class MpvPlayerCore private constructor( if (shouldReclaimAudioFocus) { val focusGranted = audioFocusManager?.requestAudioFocus() == true if (!focusGranted) { - Log.w(TAG, "Audio focus request denied for public resume") - completeFailedPublicResume( - pauseIntent, - IllegalStateException("Audio focus unavailable for resume"), - onComplete - ) + Log.w(TAG, "Audio focus request denied; keeping public resume pending") + completePublicResumeNoOp(onComplete) return } @@ -913,12 +904,8 @@ class MpvPlayerCore private constructor( } } if (interruptedAgain) { - Log.d(TAG, "Public resume interrupted by a newer audio-focus loss") - completeFailedPublicResume( - pauseIntent, - IllegalStateException("Audio focus unavailable for resume"), - onComplete - ) + Log.d(TAG, "Public resume deferred by a newer audio-focus loss") + onComplete?.invoke(Result.success(Unit)) } else { if (deferredForSurface) { Log.d(TAG, "Deferring public resume until video output is ready") @@ -949,10 +936,9 @@ class MpvPlayerCore private constructor( } } if (interruptedBeforeWrite) { - Result.failure(IllegalStateException("Audio focus unavailable for resume")) - } else { - Result.success(Unit) + Log.d(TAG, "Public resume write skipped after a newer audio-focus loss") } + Result.success(Unit) } catch (error: CancellationException) { Result.failure(error) } catch (error: Exception) { @@ -974,7 +960,7 @@ class MpvPlayerCore private constructor( synchronized(publicPauseIntentLock) { publicPauseIntentGeneration == pauseIntent.generation } - if (isCurrent && completion.isSuccess) { + if (isCurrent && completion.isSuccess && !interruptedBeforeWrite) { if (paused == true) { cachedPaused = true pausedForSurfaceLoss = false diff --git a/android/app/src/test/kotlin/com/edde746/plezy/mpv/MpvPlayerPluginTest.kt b/android/app/src/test/kotlin/com/edde746/plezy/mpv/MpvPlayerPluginTest.kt index 64b2e9b6..b5568bdb 100644 --- a/android/app/src/test/kotlin/com/edde746/plezy/mpv/MpvPlayerPluginTest.kt +++ b/android/app/src/test/kotlin/com/edde746/plezy/mpv/MpvPlayerPluginTest.kt @@ -320,7 +320,7 @@ class MpvPlayerPluginTest { } @Test - fun deniedExplicitResumeFocusRequestFailsWithoutWriting() { + fun deniedExplicitResumeFocusRequestCompletesWithoutWriting() { val writes = ConcurrentLinkedQueue>() val focusResumeCallbacks = AtomicInteger() val core = testCore { name, value -> writes += name to value } @@ -344,12 +344,13 @@ class MpvPlayerPluginTest { } awaitCondition { coreOutcome != null } - assertTrue(coreOutcome?.isFailure == true) - assertFalse(coreOutcome?.exceptionOrNull() is CancellationException) + assertTrue(coreOutcome?.isSuccess == true) assertEquals(1, coreCompletionCount.get()) assertEquals(0, writes.count { it == "pause" to "no" }) assertEquals(true, getBoolean(core, "pausedForAudioFocusLoss")) assertEquals(true, getBoolean(core, "cachedPaused")) + assertEquals(false, getBoolean(core, "desiredPaused")) + assertEquals(false, getBoolean(core, "resumeBlockedByPublicPause")) setNextAudioFocusRequestResponse(focusManager, AudioManager.AUDIOFOCUS_REQUEST_FAILED) val denied = RecordingResult() @@ -360,7 +361,7 @@ class MpvPlayerPluginTest { awaitCompletion(denied) assertEquals(1, denied.completionCount) - assertEquals("SET_PROPERTY_FAILED", denied.errorCode) + assertNull(denied.errorCode) assertEquals(0, writes.count { it == "pause" to "no" }) assertEquals(true, getBoolean(core, "pausedForAudioFocusLoss")) @@ -466,7 +467,7 @@ class MpvPlayerPluginTest { } assertTrue(blockerOutcome?.isSuccess == true) - assertTrue(resumeOutcome?.isFailure == true) + assertTrue(resumeOutcome?.isSuccess == true) assertEquals(1, resumeCompletionCount.get()) assertEquals(0, writes.count { it == "pause" to "no" }) assertEquals(true, getBoolean(core, "pausedForAudioFocusLoss"))