mirror of
https://github.com/Chevron7Locked/kima-hub.git
synced 2026-06-19 07:37:17 +00:00
fix: remove iOS auto-resume that caused earbud disconnect to route audio to speaker
The 1-second auto-resume in audio-controller.ts pause handler (added inb822375, v1.7.6) could not distinguish between system pauses that warrant resume (notification) and those that do not (audio route change when earbuds are unplugged or Bluetooth disconnects). iOS fires an identical pause event for both. When the timer fired play() after a route change, iOS routed audio to the device loudspeaker -- startling the user mid-listen. The original Control Center pause-then-play bug thatb822375targeted remained reproducible, so the auto-resume was providing no benefit while introducing this regression. MediaSession play action handler in useMediaSession.ts already covers the Control Center path with a reloadAndPlay fallback gated on a user gesture, which is the only safe way to resume on iOS. Removes: - interruptedWhilePlaying / interruptResumeTimeout / userInitiatedPause state - wasInterrupted() / clearInterruptFlag() / cancelInterruptResume() methods - 1-second setTimeout auto-resume in native pause handler - wasInterrupted() branch in foreground visibility recovery - clearInterruptFlag() call in MediaSession play action handler Preserves all other recovery paths: network retry, stall watchdog, AbortError -> reloadAndPlay, NotAllowedError -> needs-resume UI prompt, visibility/pageshow foreground resume when element is already playing.
This commit is contained in:
@@ -76,9 +76,6 @@ export function useMediaSession() {
|
||||
}
|
||||
}
|
||||
|
||||
// Clear interrupt flag — user is explicitly requesting play
|
||||
controller.clearInterruptFlag();
|
||||
|
||||
try {
|
||||
await controller.play();
|
||||
} catch {
|
||||
|
||||
@@ -46,11 +46,6 @@ export class AudioController {
|
||||
private readonly MAX_STALL_RECOVERIES = 3;
|
||||
private autoResumeAfterRecovery = false;
|
||||
|
||||
// iOS interrupt tracking
|
||||
private userInitiatedPause = false;
|
||||
private interruptedWhilePlaying = false;
|
||||
private interruptResumeTimeout: ReturnType<typeof setTimeout> | null = null;
|
||||
|
||||
// reloadAndPlay failsafe state (tracked for cleanup in destroy())
|
||||
private reloadFailsafeTimeout: ReturnType<typeof setTimeout> | null = null;
|
||||
private reloadFailsafeListener: (() => void) | null = null;
|
||||
@@ -92,8 +87,6 @@ export class AudioController {
|
||||
add("playing", () => {
|
||||
this.networkRetryCount = 0;
|
||||
this.stallRecoveryCount = 0;
|
||||
this.interruptedWhilePlaying = false;
|
||||
this.cancelInterruptResume();
|
||||
this.startWatchdog();
|
||||
this.cancelStallGrace();
|
||||
this.emit("play");
|
||||
@@ -101,25 +94,6 @@ export class AudioController {
|
||||
|
||||
add("pause", () => {
|
||||
this.stopWatchdog();
|
||||
|
||||
if (!this.userInitiatedPause && this.currentSrc && !this.audio.ended) {
|
||||
// System-initiated pause (iOS notification, phone call, control center)
|
||||
this.interruptedWhilePlaying = true;
|
||||
|
||||
// Try auto-resume after a short delay (notification sounds are brief)
|
||||
this.cancelInterruptResume();
|
||||
this.interruptResumeTimeout = setTimeout(() => {
|
||||
this.interruptResumeTimeout = null;
|
||||
if (this.interruptedWhilePlaying && this.currentSrc) {
|
||||
this.interruptedWhilePlaying = false;
|
||||
this.play().catch(() => {
|
||||
this.emit("needs-resume");
|
||||
});
|
||||
}
|
||||
}, 1000);
|
||||
}
|
||||
this.userInitiatedPause = false;
|
||||
|
||||
this.emit("pause");
|
||||
});
|
||||
|
||||
@@ -334,9 +308,6 @@ export class AudioController {
|
||||
}
|
||||
|
||||
pause(): void {
|
||||
this.userInitiatedPause = true;
|
||||
this.interruptedWhilePlaying = false;
|
||||
this.cancelInterruptResume();
|
||||
this.autoResumeAfterRecovery = false;
|
||||
this.audio.pause();
|
||||
}
|
||||
@@ -348,22 +319,6 @@ export class AudioController {
|
||||
this.cancelStallGrace();
|
||||
}
|
||||
|
||||
wasInterrupted(): boolean {
|
||||
return this.interruptedWhilePlaying;
|
||||
}
|
||||
|
||||
clearInterruptFlag(): void {
|
||||
this.interruptedWhilePlaying = false;
|
||||
this.cancelInterruptResume();
|
||||
}
|
||||
|
||||
private cancelInterruptResume(): void {
|
||||
if (this.interruptResumeTimeout) {
|
||||
clearTimeout(this.interruptResumeTimeout);
|
||||
this.interruptResumeTimeout = null;
|
||||
}
|
||||
}
|
||||
|
||||
private clearReloadFailsafe(): void {
|
||||
if (this.reloadFailsafeTimeout) {
|
||||
clearTimeout(this.reloadFailsafeTimeout);
|
||||
@@ -415,9 +370,6 @@ export class AudioController {
|
||||
}
|
||||
|
||||
stop(): void {
|
||||
this.userInitiatedPause = true;
|
||||
this.interruptedWhilePlaying = false;
|
||||
this.cancelInterruptResume();
|
||||
this.audio.pause();
|
||||
this.audio.currentTime = 0;
|
||||
}
|
||||
@@ -559,10 +511,7 @@ export class AudioController {
|
||||
this.cancelNetworkRetry();
|
||||
this.stopWatchdog();
|
||||
this.cancelStallGrace();
|
||||
this.cancelInterruptResume();
|
||||
this.clearReloadFailsafe();
|
||||
this.interruptedWhilePlaying = false;
|
||||
this.userInitiatedPause = true; // prevent cleanup pause from triggering interrupt logic
|
||||
this.audio.pause();
|
||||
this.audio.removeAttribute("src");
|
||||
this.audio.load();
|
||||
|
||||
@@ -1300,15 +1300,6 @@ export function AudioControlsProvider({ children }: { children: ReactNode }) {
|
||||
|
||||
if (ctrl.isPlaying()) {
|
||||
ctrl.tryResume().catch(() => {});
|
||||
} else if (ctrl.wasInterrupted()) {
|
||||
// iOS interrupted playback (notification, phone call, etc.)
|
||||
// The auto-resume timer in the controller may have already
|
||||
// tried, but if the app was suspended it couldn't run.
|
||||
// Try again now that we're back in foreground.
|
||||
ctrl.clearInterruptFlag();
|
||||
ctrl.play().catch(() => {
|
||||
ctrl.reloadAndPlay();
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user