From fce9466909a81756e5ad9542bc9cc3dc1e334a9c Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 18 Mar 2026 05:20:09 +0000 Subject: [PATCH 1/4] Initial plan From 74b45c1b84c95779c3696f53c257e5324010bccd Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 18 Mar 2026 05:25:51 +0000 Subject: [PATCH 2/4] fix(audio-only-mode): handle AbortError on video switch and add auto-enable on blur Co-authored-by: GrassBlock1 <46253950+GrassBlock1@users.noreply.github.com> --- .../video/audio-only-mode/AudioOnlyMode.vue | 64 ++++++++++++++++++- .../components/video/audio-only-mode/index.ts | 4 ++ 2 files changed, 66 insertions(+), 2 deletions(-) diff --git a/registry/lib/components/video/audio-only-mode/AudioOnlyMode.vue b/registry/lib/components/video/audio-only-mode/AudioOnlyMode.vue index 139e9469bf..983809031b 100644 --- a/registry/lib/components/video/audio-only-mode/AudioOnlyMode.vue +++ b/registry/lib/components/video/audio-only-mode/AudioOnlyMode.vue @@ -15,6 +15,7 @@ import { Toast } from '@/core/toast' import { Options } from './index' const console = useScopedConsole('听视频') +const BLUR_AUTO_ENABLE_DELAY_MS = 30 * 1000 export default Vue.extend({ components: { @@ -26,6 +27,7 @@ export default Vue.extend({ isAudioMode: false, disabled: false, settings, + blurTimer: null as ReturnType | null, } }, computed: { @@ -37,9 +39,12 @@ export default Vue.extend({ }, }, async mounted() { - videoChange(() => { + videoChange(async () => { this.isAudioMode = false if (this.settings.options.autoEnable) { + // Wait briefly for the player to settle after a no-refresh video change + // before attempting to switch to audio mode, to avoid AbortError. + await new Promise(r => setTimeout(r, 800)) this.switchToAudioMode() } }) @@ -53,8 +58,49 @@ export default Vue.extend({ this.switchToAudioMode() } }) + + if (this.settings.options.autoEnableOnBlur) { + this.setupBlurListener() + } + + addComponentListener('audioOnlyMode.autoEnableOnBlur', (value: boolean) => { + if (value) { + this.setupBlurListener() + } else { + this.teardownBlurListener() + } + }) + }, + beforeDestroy() { + this.teardownBlurListener() }, methods: { + setupBlurListener() { + // Remove first to prevent duplicate registrations if called multiple times. + document.removeEventListener('visibilitychange', this.handleVisibilityChange) + document.addEventListener('visibilitychange', this.handleVisibilityChange) + }, + teardownBlurListener() { + document.removeEventListener('visibilitychange', this.handleVisibilityChange) + this.clearBlurTimer() + }, + handleVisibilityChange() { + if (document.hidden) { + this.blurTimer = setTimeout(() => { + if (!this.isAudioMode) { + this.switchToAudioMode() + } + }, BLUR_AUTO_ENABLE_DELAY_MS) + } else { + this.clearBlurTimer() + } + }, + clearBlurTimer() { + if (this.blurTimer !== null) { + clearTimeout(this.blurTimer) + this.blurTimer = null + } + }, async toggleAudioMode() { if (this.isAudioMode) { Toast.info('请刷新页面以退出音频模式', '听视频', 2000) @@ -179,10 +225,24 @@ export default Vue.extend({ video.src = audioUrl video.load() - await video.play().catch((err: Error) => { + + let playAborted = false + await video.play().catch((err: DOMException) => { + if (err.name === 'AbortError') { + // AbortError is expected when a no-refresh video navigation interrupts + // the play() call before it can complete. This is not a real failure; + // the next videoChange event will trigger another switch attempt. + console.warn('播放被中止 (AbortError),可能由视频切换引起,将等待下次触发') + playAborted = true + return + } throw new Error(`播放失败: ${err.message}`) }) + if (playAborted) { + return + } + this.isAudioMode = true Toast.success('已切换到音频模式', '听视频', 2000) console.log('已切换到音频模式') diff --git a/registry/lib/components/video/audio-only-mode/index.ts b/registry/lib/components/video/audio-only-mode/index.ts index fa4fb65d6d..3cd28f889a 100644 --- a/registry/lib/components/video/audio-only-mode/index.ts +++ b/registry/lib/components/video/audio-only-mode/index.ts @@ -11,6 +11,10 @@ export const options = defineOptionsMetadata({ defaultValue: false, displayName: '自动启用', }, + autoEnableOnBlur: { + defaultValue: false, + displayName: '失去焦点后自动启用', + }, rememberProgress: { defaultValue: true, displayName: '记住播放进度', From 63790b5be3902824de2582e4dc354966910e1829 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=8D=89=E6=96=B9=E5=9D=97?= <46253950+GrassBlock1@users.noreply.github.com> Date: Wed, 18 Mar 2026 15:56:38 +0800 Subject: [PATCH 3/4] feat(audio-only): add a guard so only one auto-enable attempt runs on initial mount Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- .../lib/components/video/audio-only-mode/AudioOnlyMode.vue | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/registry/lib/components/video/audio-only-mode/AudioOnlyMode.vue b/registry/lib/components/video/audio-only-mode/AudioOnlyMode.vue index 983809031b..71f4aba691 100644 --- a/registry/lib/components/video/audio-only-mode/AudioOnlyMode.vue +++ b/registry/lib/components/video/audio-only-mode/AudioOnlyMode.vue @@ -28,6 +28,7 @@ export default Vue.extend({ disabled: false, settings, blurTimer: null as ReturnType | null, + initialAutoEnableAttempted: false as boolean, } }, computed: { @@ -41,7 +42,8 @@ export default Vue.extend({ async mounted() { videoChange(async () => { this.isAudioMode = false - if (this.settings.options.autoEnable) { + if (this.settings.options.autoEnable && !this.initialAutoEnableAttempted) { + this.initialAutoEnableAttempted = true // Wait briefly for the player to settle after a no-refresh video change // before attempting to switch to audio mode, to avoid AbortError. await new Promise(r => setTimeout(r, 800)) @@ -49,7 +51,8 @@ export default Vue.extend({ } }) - if (this.settings.options.autoEnable) { + if (this.settings.options.autoEnable && !this.initialAutoEnableAttempted) { + this.initialAutoEnableAttempted = true await this.switchToAudioMode() } From 5db71d023b6124717322c48f1ca12e3f1b4b4a45 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=8D=89=E6=96=B9=E5=9D=97?= <46253950+GrassBlock1@users.noreply.github.com> Date: Wed, 18 Mar 2026 15:57:28 +0800 Subject: [PATCH 4/4] fix(audio-only): properly handle timeouts Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- .../lib/components/video/audio-only-mode/AudioOnlyMode.vue | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/registry/lib/components/video/audio-only-mode/AudioOnlyMode.vue b/registry/lib/components/video/audio-only-mode/AudioOnlyMode.vue index 71f4aba691..5cebf43d96 100644 --- a/registry/lib/components/video/audio-only-mode/AudioOnlyMode.vue +++ b/registry/lib/components/video/audio-only-mode/AudioOnlyMode.vue @@ -89,8 +89,11 @@ export default Vue.extend({ }, handleVisibilityChange() { if (document.hidden) { + // Clear any existing timer before starting a new one to avoid orphaned timeouts. + this.clearBlurTimer() this.blurTimer = setTimeout(() => { - if (!this.isAudioMode) { + // Re-check visibility to avoid switching modes if the page is visible again. + if (document.hidden && !this.isAudioMode) { this.switchToAudioMode() } }, BLUR_AUTO_ENABLE_DELAY_MS)